Патчим gnupg или пара RSA-32768 за 106 минут

security

На самом деле патчим gnupg и libgcrypt…

Когда-то давным давно, чтобы использовать 8192 и 16384 RSA ключи я правил размер в keygen.c и размер SECMEM буффера по соседству. Дела давно минувших дней, теперь SECMEM вынесена в config.h и именуется SECMEM_BUFFER_SIZE.

В итоге после скачивания верии 2.0.29 под свежий debian 8.3, за место убитой 12й убунты апдейтом на 14ую, я быстренько подкрутил размер ключика и размер буфера и радостно сгенерировал на 5200U 16 кбит ключ за 18 (или 19) минут, что раньше занимало 45–50 минут на P6200.

Но вот 32 кбит дали мне пачку ошибок. Свободное время есть — разбираемся.
Первое, что бросается в глаза, это жестко прописанный размер при выделении SECMEM’ов. Вот несколько:

agent/gpg-agent.c: gcry_control (GCRYCTL_INIT_SECMEM, 32768, 0); — 4 килобайта (так как эта реализация использует значение в битах)
scd/scdaemon.c: gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
tools/gpg-check-pattern.c: gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);

В принципе экономия памяти дело хорошее… с другой стороны если памяти хватает, почему не выделить полный объем, равный константе SECMEM_BUFFER_SIZE.

Далее в программе полно мест где фигурируют то 4 кб ключи или связанные с ними (выделение буфера на чтение, или создания «пакета» для записи), привожу несколько:

agent/command-ssh.c: log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
g10/card-util.c: n = get_data_from_file (args, 16384, &data);
g10/plaintext.c: byte *buffer = xmalloc( 32768 );
common/dns-cert.c: rc=get_dns_cert (argv[1],16384,&iobuf,&fpr,&fpr_len,&url);

Сам размер ключа, который программа ест, задается в g10/keygen.c:

const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096);
unsigned int nbits, min, def = DEFAULT_STD_KEYSIZE, max=4096;

Но, исправив исходники gnupg при генерации ключа RSA-32768 мы получим:

gpg: checking the trustdb
gpg: keyring_get_keyblock: read error: Invalid packet
gpg: keyring_get_keyblock failed: Invalid keyring
gpg: failed to rebuild keyring cache: Invalid keyring
gpg: keydb_search failed: Invalid packet
gpg: public key of ultimately trusted key FB2E6BDF not found
gpg: keyring_get_keyblock: read error: Invalid packet
gpg: keydb_get_keyblock failed: Invalid keyring
gpg: keydb_search failed: Invalid keyring
gpg: public key of ultimately trusted key 6146D68D not found
gpg: 3 marginal (s) needed, 1 complete (s) needed, PGP trust model
gpg: keyring_get_keyblock: read error: Invalid packet
gpg: keydb_get_keyblock failed: Invalid keyring
gpg: validate_key_list failed


Дьявол кроется в деталях в libgcrypt:

mpi/mpicoder.c: #define MAX_EXTERN_MPI_BITS 16384 — размер пакета в битах.

И в некоем роде:
src/secmem.c: #define MINIMUM_POOL_SIZE 16384
src/secmem.c: #define STANDARD_POOL_SIZE 32768
В некоем потому, что, как я понял из кода, это размер пула для SECMEM в байтах, в котором gnupg инициализирует свой securememory через gcry_control (GCRYCTL_INIT_SECMEM, размер_в_битах, 0);

В конечном итоге модификация вылилась вот в такой diff для GnuPG-2.0.29

gnupg-2.0.29-RSA32k.patch
diff -uraN a/agent/command-ssh.c b/agent/command-ssh.c
--- a/agent/command-ssh.c       2015-09-08 14:39:24.000000000 +0200
+++ b/agent/command-ssh.c       2016-02-26 21:46:47.000000000 +0100
@@ -592,7 +592,7 @@
      not too large. */
   if (mpi_data_size > 520)
     {
-      log_error (_("ssh keys greater than %d bits are not supported\n"), 4096);
+      log_error (_("ssh keys greater than %d bits are not supported\n"), KEY_MAX_SIZE_LOOKSLIKE);
       err = GPG_ERR_TOO_LARGE;
       goto out;
     }
diff -uraN a/agent/gpg-agent.c b/agent/gpg-agent.c
--- a/agent/gpg-agent.c 2015-09-08 14:39:24.000000000 +0200
+++ b/agent/gpg-agent.c 2016-02-26 21:46:47.000000000 +0100
@@ -233,7 +233,7 @@
 /* To avoid surprises we limit the size of the mapped IPC file to this
    value.  Putty currently (0.62) uses 8k, thus 16k should be enough
    for the foreseeable future.  */
-#define PUTTY_IPC_MAXLEN 16384
+#define PUTTY_IPC_MAXLEN KEY_MAX_SIZE_LOOKSLIKE
 #endif /*HAVE_W32_SYSTEM*/
 
 /* The list of open file descriptors at startup.  Note that this list
@@ -743,7 +743,7 @@
     }
 
   /* Initialize the secure memory. */
-  gcry_control (GCRYCTL_INIT_SECMEM, 32768, 0);
+  gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
   maybe_setuid = 0;
 
   /*
diff -uraN a/agent/protect-tool.c b/agent/protect-tool.c
--- a/agent/protect-tool.c      2015-09-08 14:39:24.000000000 +0200
+++ b/agent/protect-tool.c      2016-02-26 21:46:47.000000000 +0100
@@ -1036,7 +1036,7 @@
     }
 
   setup_libgcrypt_logging ();
-  gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+  gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
 
 
   opt_homedir = default_homedir ();
diff -uraN a/common/dns-cert.c b/common/dns-cert.c
--- a/common/dns-cert.c 2015-09-08 14:39:24.000000000 +0200
+++ b/common/dns-cert.c 2016-02-26 21:46:47.000000000 +0100
@@ -305,7 +305,7 @@
 
   printf("CERT lookup on %s\n",argv[1]);
 
-  rc=get_dns_cert (argv[1],16384,&iobuf,&fpr,&fpr_len,&url);
+  rc=get_dns_cert (argv[1],KEY_MAX_SIZE_LOOKSLIKE,&iobuf,&fpr,&fpr_len,&url);
   if(rc==-1)
     printf("error\n");
   else if(rc==0)
diff -uraN a/config.h.in b/config.h.in
--- a/config.h.in       2015-09-08 15:30:25.000000000 +0200
+++ b/config.h.in       2016-02-26 21:46:47.000000000 +0100
@@ -608,6 +608,9 @@
 /* Size of secure memory buffer */
 #undef SECMEM_BUFFER_SIZE
 
+/* Maximum key size or lookslike */
+#undef KEY_MAX_SIZE_LOOKSLIKE
+
 /* defines the filename of the shred program */
 #undef SHRED
 
diff -uraN a/configure b/configure
--- a/configure 2015-09-08 16:12:06.000000000 +0200
+++ b/configure 2016-02-26 21:46:47.000000000 +0100
@@ -5307,13 +5307,13 @@
 { $as_echo "$as_me:${as_lineno-$LINENO}: result: $large_secmem" >&5
 $as_echo "$large_secmem" >&6; }
 if test "$large_secmem" = yes ; then
-   SECMEM_BUFFER_SIZE=65536
+   SECMEM_BUFFER_SIZE=262144
 else
-   SECMEM_BUFFER_SIZE=32768
+   SECMEM_BUFFER_SIZE=262144
 fi
-
 cat >>confdefs.h <<_ACEOF
 #define SECMEM_BUFFER_SIZE $SECMEM_BUFFER_SIZE
+#define KEY_MAX_SIZE_LOOKSLIKE 32768
 _ACEOF
 
 
diff -uraN a/configure.ac b/configure.ac
--- a/configure.ac      2015-09-08 14:39:24.000000000 +0200
+++ b/configure.ac      2016-02-26 21:46:47.000000000 +0100
@@ -183,12 +183,15 @@
               large_secmem=$enableval, large_secmem=no)
 AC_MSG_RESULT($large_secmem)
 if test "$large_secmem" = yes ; then
-   SECMEM_BUFFER_SIZE=65536
+   SECMEM_BUFFER_SIZE=262144
 else
-   SECMEM_BUFFER_SIZE=32768
+   SECMEM_BUFFER_SIZE=262144
 fi
 AC_DEFINE_UNQUOTED(SECMEM_BUFFER_SIZE,$SECMEM_BUFFER_SIZE,
                    [Size of secure memory buffer])
+                   
+AC_DEFINE_UNQUOTED(KEY_MAX_SIZE_LOOKSLIKE,32768,
+                   [Maximum key size or lookslike])                   
 
 
 # Allow disabling of bzib2 support.
diff -uraN a/doc/gnupg.info-1 b/doc/gnupg.info-1
--- a/doc/gnupg.info-1  2015-09-08 16:15:29.000000000 +0200
+++ b/doc/gnupg.info-1  2016-02-26 21:46:47.000000000 +0100
@@ -2552,7 +2552,7 @@
 
      max-cert-size
           When retrieving a key via DNS CERT, only accept keys up to
-          this size.  Defaults to 16384 bytes.
+          this size.  Defaults to 32768 bytes.
 
      debug
           Turn on debug output in the keyserver helper program.  Note
diff -uraN a/doc/gpg.texi b/doc/gpg.texi
--- a/doc/gpg.texi      2015-09-08 14:39:24.000000000 +0200
+++ b/doc/gpg.texi      2016-02-26 21:46:47.000000000 +0100
@@ -1645,7 +1645,7 @@
 @ifclear gpgtwoone
   @item max-cert-size
   When retrieving a key via DNS CERT, only accept keys up to this size.
-  Defaults to 16384 bytes.
+  Defaults to 32768 bytes.
 @end ifclear
 
   @item debug
diff -uraN a/g10/card-util.c b/g10/card-util.c
--- a/g10/card-util.c   2015-09-08 14:39:24.000000000 +0200
+++ b/g10/card-util.c   2016-02-26 21:46:47.000000000 +0100
@@ -946,7 +946,7 @@
     {
       for (args++; spacep (args); args++)
         ;
-      n = get_data_from_file (args, 16384, &data);
+      n = get_data_from_file (args, KEY_MAX_SIZE_LOOKSLIKE, &data);
       if (n < 0) return -1; } @@ -1285,7 +1285,7 @@ ask_card_keysize (int keyno, unsigned int nbits) { unsigned int min_nbits = 1024; - unsigned int max_nbits = 4096; + unsigned int max_nbits = KEY_MAX_SIZE_LOOKSLIKE; char *prompt, *answer; unsigned int req_nbits; diff -uraN a/g10/gpg.h b/g10/gpg.h --- a/g10/gpg.h 2015-09-08 14:39:24.000000000 +0200 +++ b/g10/gpg.h 2016-02-26 21:46:47.000000000 +0100 @@ -35,7 +35,7 @@ /* Number of bits we accept when reading or writing MPIs. */ -#define MAX_EXTERN_MPI_BITS 16384 +#define MAX_EXTERN_MPI_BITS KEY_MAX_SIZE_LOOKSLIKE /* The maximum length of a binary fingerprints. */ #define MAX_FINGERPRINT_LEN 20 diff -uraN a/g10/keygen.c b/g10/keygen.c --- a/g10/keygen.c 2015-09-08 14:39:24.000000000 +0200 +++ b/g10/keygen.c 2016-02-26 21:46:47.000000000 +0100 @@ -1429,7 +1429,7 @@ PKT_secret_key *sk; PKT_public_key *pk; gcry_sexp_t s_parms, s_key; - const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096); + const unsigned maxsize = KEY_MAX_SIZE_LOOKSLIKE; assert (is_RSA(algo)); @@ -1798,7 +1798,7 @@ static unsigned ask_keysize (int algo, unsigned int primary_keysize) { - unsigned int nbits, min, def = DEFAULT_STD_KEYSIZE, max=4096; + unsigned int nbits, min, def = DEFAULT_STD_KEYSIZE, max=KEY_MAX_SIZE_LOOKSLIKE; int for_subkey = !!primary_keysize; int autocomp = 0; diff -uraN a/g10/keyserver.c b/g10/keyserver.c --- a/g10/keyserver.c 2015-09-08 14:39:24.000000000 +0200 +++ b/g10/keyserver.c 2016-02-26 21:46:47.000000000 +0100 @@ -94,7 +94,7 @@ struct keyserver_spec *keyserver); /* Reasonable guess */ -#define DEFAULT_MAX_CERT_SIZE 16384 +#define DEFAULT_MAX_CERT_SIZE KEY_MAX_SIZE_LOOKSLIKE static size_t max_cert_size=DEFAULT_MAX_CERT_SIZE; diff -uraN a/g10/parse-packet.c b/g10/parse-packet.c --- a/g10/parse-packet.c 2015-09-08 14:39:24.000000000 +0200 +++ b/g10/parse-packet.c 2016-02-26 21:46:47.000000000 +0100 @@ -1681,7 +1681,7 @@ --*length; nbits |= c; - if (nbits > 16384)
+  if (nbits > KEY_MAX_SIZE_LOOKSLIKE)
     {
       log_error ("mpi too large (%u bits)\n", nbits);
       return NULL;
diff -uraN a/g10/plaintext.c b/g10/plaintext.c
--- a/g10/plaintext.c   2015-09-08 14:39:24.000000000 +0200
+++ b/g10/plaintext.c   2016-02-26 21:46:47.000000000 +0100
@@ -225,9 +225,9 @@
            }
        }
        else { /* binary mode */
-           byte *buffer = xmalloc( 32768 );
+           byte *buffer = xmalloc( KEY_MAX_SIZE_LOOKSLIKE );
            while( pt->len ) {
-               int len = pt->len > 32768 ? 32768 : pt->len;
+               int len = pt->len > KEY_MAX_SIZE_LOOKSLIKE ? KEY_MAX_SIZE_LOOKSLIKE : pt->len;
                len = iobuf_read( pt->buf, buffer, len );
                if( len == -1 ) {
                     rc = gpg_error_from_syserror ();
@@ -294,7 +294,7 @@
            }
        }
        else { /* binary mode */
-           byte *buffer = xmalloc( 32768 );
+           byte *buffer = xmalloc( KEY_MAX_SIZE_LOOKSLIKE );
            int eof_seen = 0;
 
            while ( !eof_seen ) {
@@ -304,10 +304,10 @@
                 * off and therefore we don't catch the boundary.
                 * So, always assume EOF if iobuf_read returns less bytes
                 * then requested */
-               int len = iobuf_read( pt->buf, buffer, 32768 );
+               int len = iobuf_read( pt->buf, buffer, KEY_MAX_SIZE_LOOKSLIKE );
                if( len == -1 )
                    break;
-               if( len < 32768 )
+               if( len < KEY_MAX_SIZE_LOOKSLIKE ) eof_seen = 1; if( mfx->md )
                    gcry_md_write ( mfx->md, buffer, len );
diff -uraN a/scd/apdu.c b/scd/apdu.c
--- a/scd/apdu.c        2015-09-08 14:39:24.000000000 +0200
+++ b/scd/apdu.c        2016-02-26 21:46:47.000000000 +0100
@@ -2964,7 +2964,7 @@
 
   if (opt.ctapi_driver && *opt.ctapi_driver)
     {
-      int port = portstr? atoi (portstr) : 32768;
+      int port = portstr? atoi (portstr) : KEY_MAX_SIZE_LOOKSLIKE;
 
       if (!ct_api_loaded)
         {
@@ -3612,7 +3612,7 @@
       else if (extended_mode < 0) { /* Send APDU using chaining mode. */ - if (lc > 16384)
+          if (lc > KEY_MAX_SIZE_LOOKSLIKE)
             return SW_WRONG_LENGTH;   /* Sanity check.  */
           if ((class&0xf0) != 0)
             return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0.  */
diff -uraN a/scd/command.c b/scd/command.c
--- a/scd/command.c     2015-09-08 14:39:24.000000000 +0200
+++ b/scd/command.c     2016-02-26 21:46:47.000000000 +0100
@@ -45,13 +45,13 @@
 #define MAXLEN_PIN 100
 
 /* Maximum allowed size of key data as used in inquiries. */
-#define MAXLEN_KEYDATA 4096
+#define MAXLEN_KEYDATA KEY_MAX_SIZE_LOOKSLIKE
 
 /* Maximum allowed total data size for SETDATA.  */
-#define MAXLEN_SETDATA 4096
+#define MAXLEN_SETDATA KEY_MAX_SIZE_LOOKSLIKE
 
 /* Maximum allowed size of certificate data as used in inquiries. */
-#define MAXLEN_CERTDATA 16384
+#define MAXLEN_CERTDATA KEY_MAX_SIZE_LOOKSLIKE
 
 
 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
diff -uraN a/scd/scdaemon.c b/scd/scdaemon.c
--- a/scd/scdaemon.c    2015-09-08 14:39:24.000000000 +0200
+++ b/scd/scdaemon.c    2016-02-26 21:46:47.000000000 +0100
@@ -497,7 +497,7 @@
     }
 
   /* initialize the secure memory. */
-  gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+  gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
   maybe_setuid = 0;
 
   /*
diff -uraN a/sm/gpgsm.c b/sm/gpgsm.c
--- a/sm/gpgsm.c        2015-09-08 14:39:24.000000000 +0200
+++ b/sm/gpgsm.c        2016-02-26 21:46:47.000000000 +0100
@@ -965,7 +965,7 @@
 
 
   /* Initialize the secure memory. */
-  gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+  gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
   maybe_setuid = 0;
 
   /*
diff -uraN a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c
--- a/tools/gpg-check-pattern.c 2015-09-08 14:39:24.000000000 +0200
+++ b/tools/gpg-check-pattern.c 2016-02-26 21:46:47.000000000 +0100
@@ -179,7 +179,7 @@
     }
 
   setup_libgcrypt_logging ();
-  gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
+  gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
 
   opt.homedir = default_homedir ();
 
diff -uraN a/tools/make-dns-cert.c b/tools/make-dns-cert.c
--- a/tools/make-dns-cert.c     2015-09-01 08:52:21.000000000 +0200
+++ b/tools/make-dns-cert.c     2016-02-26 21:46:47.000000000 +0100
@@ -64,7 +64,7 @@
       goto fail;
     }
 
-  if(statbuf.st_size>16384)
+  if(statbuf.st_size>KEY_MAX_SIZE_LOOKSLIKE)
     fprintf(stderr,"Warning: key file %s is larger than the default"
            " GnuPG max-cert-size\n",keyfile);
 
diff -uraN a/tools/symcryptrun.c b/tools/symcryptrun.c
--- a/tools/symcryptrun.c       2015-09-08 14:39:24.000000000 +0200
+++ b/tools/symcryptrun.c       2016-02-26 21:46:47.000000000 +0100
@@ -999,7 +999,7 @@
                  NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
     }
   setup_libgcrypt_logging ();
-  gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
+  gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
 
   /* Tell simple-pwquery about the the standard socket name.  */
   {


До кучи увеличен максимальный размер сертификата и ключа для ssh.

И вот такой diff для libgcrypt-1.6.5

libgcrypt-1.6.5-RSA32k.patch
diff -uraN a/mpi/mpicoder.c b/mpi/mpicoder.c
--- a/mpi/mpicoder.c    2015-02-23 11:55:58.000000000 +0100
+++ b/mpi/mpicoder.c    2016-02-25 17:45:29.000000000 +0100
@@ -27,7 +27,7 @@
 #include "mpi-internal.h"
 #include "g10lib.h"
 
-#define MAX_EXTERN_MPI_BITS 16384
+#define MAX_EXTERN_MPI_BITS 32768
 
 /* Helper used to scan PGP style MPIs.  Returns NULL on failure. */
 static gcry_mpi_t
diff -uraN a/src/secmem.c b/src/secmem.c
--- a/src/secmem.c      2016-02-09 10:10:38.000000000 +0100
+++ b/src/secmem.c      2016-02-25 17:45:29.000000000 +0100
@@ -45,8 +45,8 @@
 #define MAP_ANONYMOUS MAP_ANON
 #endif
 
-#define MINIMUM_POOL_SIZE 16384
-#define STANDARD_POOL_SIZE 32768
+#define MINIMUM_POOL_SIZE 32768
+#define STANDARD_POOL_SIZE 65536
 #define DEFAULT_PAGE_SIZE 4096
 
 typedef struct memblock


Размер пула был удвоен. Для почти безглюковых 16384 ключей размер пула был 32768 (при норме ключей в 4096 и 8192).

Работает ли сие?

Да:

sec 32768R/18B54A62 2016–02–24

uid test32768key

ssb 32768R/1507CD2A 2016–02–24


Производительность на i5–5200 под Debian 8.3 (4.3.0–0.bpo.1-amd64 #1 SMP Debian 4.3.3–7~bpo8+1 (2016–01–19) x86_64 GNU/Linux):

RSA 16384 — 19 минут (время генерации пары)

RSA 32768 — 106 минут (время генерации пары)


Шифрование файла размером 12Мб (другой ключ RSA-32768, за время тестов и написания я их штук 10 нагенерил):

time gpg2 --out file.gz.enc --recipient «test32768pair» --encrypt file.gz

real 0m0.079s

user 0m0.072s

sys 0m0.004s


Дешифровка:

time gpg2 --out file.gz.gz --decrypt file.gz.enc

real 0m7.610s

user 0m5.624s

sys 0m0.024s


И проверка:

7ab98fd4a154fad5f5bbe0d698178783cd2ac994 file.gz

9773bb1b9d7f75f408f562d476e8936aafa0f3b9 file.gz.enc

7ab98fd4a154fad5f5bbe0d698178783cd2ac994 file.gz.gz


Тонкости: если в кейринг добавить такой ключ, то ванильная версия не будет читать, в виду описанного выше (mpi и т.д.). Так что при использовании такой версии (модифицированной) и использовании ванильного софта (который работает с классическими ключами) надо создавать отдельный кейринг.

Ну и, как обычно: ВОЗМОЖНЫ ПОТЕРЯ ДАННЫХ, КРАХ СИСТЕМЫ И КРАСНЫЕ ГЛАЗА.

Ссылки на проект и патчи (=diff) на github:


Удачи и при наличии вопросов я готов на них ответить, со своих двухлетним опытом C который был 15 лет назад!

Так же выслушаю замечания по улучшению модификации.

Вопрос: зачем RSA-32768, вам что RSA-(2048×4096|8192×16384) мало?

Ответ: потому, что *можно!

*современное железо вполне себе позволяет работать с большими ключами без ощутимых проблем. Даже ноутбучное. Призывать использоват RSA-32768 я не буду, но иметь такой большой ключ и пачку сабключей идея неплохая, как и шифровать особо критичные данные, пока великий КвантовыйКомпьютер не покорал мир простых чисел!

© Habrahabr.ru