Domanda di base su OpenSSL e AES-GCM

2

Durante la ricerca su come crittografare le chiavi private per le connessioni SSH nel modo più sicuro possibile, ho incontrato i seguenti problemi di comprensione di base (Nota: ho usato la più recente versione stabile 1.0.2h di OpenSSL per la mia ricerca):

  1. man enc afferma che l'utility enc non supporta le modalità di crittografia autenticate come GCM. D'altra parte, sto usando crittografi TLS 1.2 come ECDHE-RSA-AES128-GCM-SHA256 sul mio server web che usa OpenSSL per crittografare le connessioni, e ho verificato che tali cifrari siano effettivamente negoziate quando ci si collega tramite SSL / TLS al rispettivo sito web.

    Quindi OpenSSL è ovviamente in grado di crittografare i dati in modalità AESxxx-GCM. Mi sarei aspettato che OpenSSL utilizzasse lo stesso codice per crittografare i dati quando chiamato tramite openssl enc poiché utilizza per crittografare i dati mentre fornisce funzionalità SSL / TLS ai server Web.

    Ho frainteso qualcosa? Qual è il motivo per cui OpenSSL supporta AESxxx-GCM mentre esegue TLS / SSL, ma non può crittografare i dati direttamente in questo modo quando viene chiamato tramite la riga di comando?

  2. Un metodo comune per visualizzare i nomi di crittografia disponibili (non le suite di crittografia) consiste nel chiamare openssl enc con un parametro errato, ad es. %codice%. In questo modo openssl stampa un breve aiuto, inclusi i nomi di cifratura disponibili. Nel mio caso, l'output include più crittografie AES-GCM. Perché openssl enc --help afferma di supportare tali cifrari mentre la rispettiva pagina man afferma che non è così?

posta Binarus 01.07.2016 - 10:35
fonte

2 risposte

5

OpenSSL implementa quasi una dozzina di algoritmi simmetrici e diverse dozzine di combinazioni in modalità cipher, ma fornisce una (quasi) singola interfaccia a tutti loro nel modulo EVP ( cioè la funzione esterna ei nomi dei tipi che iniziano con EVP_ ) documentati qui online o nel (reticolato) man page per EVP_{Cipher,Encrypt,Decrypt}* e EVP_CIPHER_* e EVP_CIPHER_CTX_* su qualsiasi sistema Unix con OpenSSL installato. (Se lo hai costruito / installato da te piuttosto che usare il gestore pacchetti o equivalente per il tuo SO o distro, potresti dover usare MANPATH o altre opzioni di man per trovare le pagine man.) Analogamente numerosi algoritmi di digest / hash e public- gli algoritmi di crittografia e firma chiave (e ibrida) sono accessibili tramite interfacce generiche; vedi man evp . Tuttavia, le modalità AEAD (GCM e CCM, oltre a OCB pianificate in 1.1.0) non si adattano esattamente all'API generica e richiedono ulteriori chiamate di "controllo", consulta le sezioni " Modalità GCM e OCB 'e' Modalità CCM 'nella pagina man sopra.

Il modulo SSL-and-TLS (directory di primo livello ssl/ nell'origine) contiene il codice in t1_enc.c che fa% diversoEVP chiama suite AEAD (implementate), nello stesso modo in cui gestisce anche le variazioni per cifrari CBC e stream, cifrari "esportazione" obsoleti ma ancora codificati, TLS1.1 rispetto alla precedente gestione IV e altre opzioni e varianti di protocollo.

Ma la riga di comando enc in apps/enc.c utilizza solo l'interfaccia generica e non gli speciali di AEAD, sebbene ci sia una voce non assegnata nel tracker della richiesta (login guest / guest) per aggiungere questo. Le utilità della riga di comando in generale sono wrapper abbastanza limitati per quanto riguarda la funzionalità in libssl e libcrypto e, se si desidera qualcosa di completo, lucido, conveniente, ecc. L'idea è di modificarla o sostituirla. In questo caso dovresti (definire) la modalità con cui il tag, e possibilmente AAD, viene gestito nel formato di file ciphertext, che al momento è semplice al punto da essere banale - e ricorda qualsiasi modifica che non funzioni con il probabilmente milioni di% di file di co_de archiviati dagli utenti negli ultimi due decenni non saranno accettati da nessuno tranne te.

Ricorda anche che enc con una password (non la chiave effettiva e IV utilizzando enc maiuscolo e -K ) utilizza un PBKDF molto scarso , una variante di PBKDF1 vedi EVP_BytesToKey con solo una iterazione .
Vedere openssl: recupera chiave e IV per passphrase
e openssl enc usa md5 per hash password e il sale
e link (divulgazione: mia). Preoccuparsi dell'utilizzo di algoritmi di best practice come AES256-GCM con questo PBKDF è come la proverbiale doratura di uno stronzo di mucca.

Il messaggio di utilizzo per -iv elenca tutti i cipher / modi simmetrici in EVP, anche quelli openssl enc -invalid non supporta. Se ti interessa, puoi segnalarlo come un bug. enc (spazio pianificato invece del primo trattino in 1.1.0) fa lo stesso, ma openssl list-cipher-algorithms (idem) elenca solo quelli utilizzabili come comandi, che esclude quelli AEAD.

Infine, hai menzionato ma in realtà non chiedi di SSH. Se intendi OpenSSH (che non è l'unico SSH), FYI OpenSSH prima del 6.5% di co_de effettivamente utilizza OpenSSL libcrypto per scrivere privatekeys nei formati 'legacy' di OpenSSL (tipi PEM openssl list-cipher-commands , ssh-keygen , RSA PRIVATE KEY ) che usano anche DSA PRIVATE KEY con una iterazione. Ma EC PRIVATE KEY e EVP_BytesToKey e ssh-keygen usando le routine di read di OpenSSL possono anche gestire il 'nuovo' (circa 2000?) PKCS # 8 di OpenSSL in formato crittografato, tipo PEM ssh , che può utilizzare PBKDF2 con 2048 iterazioni tramite 1.0.2 (buona circa 2000, appena sufficiente ora) e pianificata configurabile fino a INT_MAX in 1.1.0. OpenSSH all'inizio 6.5 ha un'opzione sshd per il proprio formato (non ASN.1 ma ancora PEM) utilizzando bcrypt e impone l'opzione per il tipo di chiave ed25519 (che OpenSSL non supporta, almeno non ancora).

    
risposta data 04.07.2016 - 04:06
fonte
1

Perché fino ad ora, openssl enc non supporta AES-256-GCM, ho scritto il seguente codice sorgente C per fare ciò che farebbe openssl enc :

(compila in questo modo 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;
}

Ed ecco come decifrare ciò che il programma precedente ha crittografato:

(compila in questo modo 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;
}
    
risposta data 07.08.2017 - 20:51
fonte

Leggi altre domande sui tag