Знакомство с библиотекой шифрования libgcrypt

Добрый день, хабрахабр!

imageВ процессе написания одной из своих программ мне понадобилось разобраться с библиотекой шифрование и де-шифрования текста. Я разобрался и теперь хочу поделиться накопленным опытом и знаниями с сообществом.

В данной статье речь пойдет о библиотеке libgcrypt.

Предисловие


Программу я пишу под ОС Linux. Поэтому и библиотеку искал тоже под эту ОС. Я не пытался найти десятки библиотек, чтобы потом выбрать лучшую. Я выбрал ту, которая подходила под мои нужды — раз, которая используется в достаточно известных продуктах — два.

О самой библиотеке libgcrypt


Библиотека предоставляет высокоуровневый интерфейс к механизмам криптографии низкого уровня. Проще говоря, вы выбираете необходимый вам механизм шифрования, придумываете пароль и кодируете текст, не вникая в то как работает выбранный вами алгоритм.
Библиотека написана в рамках проекта GnuPG и распространяется по лицензии LGPL.

Процесс шифрования


В процессе шифрования мы будем использовать следующие функции (указаны в порядке использования):
  1. gcry_cipher_open — создаем дескриптор контекста
  2. gcry_cipher_setkey — задаем пароль
  3. gcry_cipher_setiv — задаем вектор инициализации
  4. gcry_cipher_encrypt — функция шифрования текста
  5. gcry_cipher_close — закрытие дескриптора контекста

Теперь подробнее о каждой функции.

gcry_error_t gcry_cipher_open (gcry_cipher_hd_t *hd, int algo, int mode, unsigned int flags)

Функция создает дескриптор контекста, который необходим для дальнейших функций шифрования и возвращает дескриптор в ‘hd’. В случае ошибки возвращается ненулевой код ошибки.

hd — указатель на наш будущий дескриптор контекста.

algo — алгоритм, который мы собираемся использовать для шифрования текста. Примеры:
GCRY_CIPHER_IDEA — алгоритм IDEA. Хоть его и можно выбрать, но работать не будет. Так как алгоритм запатентованный, для него нет реализации в свободной библиотеке.
GCRY_CIPHER_3DES — (Triple-DES with 3 Keys as EDE) симметричный блочный шифр.
GCRY_CIPHER_BLOWFISH — алгоритм Blowfish. Текущая реализация позволяет использовать только 128-битный ключ.
Так же есть RIJNDAEL, TWOFISH, AES, SERPENT и так далее.
(Весь список алгоритов находить здесь )

mode — один из следующих вариантов:
  • GCRY_CIPHER_MODE_NONE — не использовать никакой модификатор. (Надо избегать использования этого ключа.)
  • GCRY_CIPHER_MODE_ECB — режим электронной кодовой книги. (Electronic Code Book)
  • GCRY_CIPHER_MODE_CFB — режим обратной связи по шифротексту. (Cipher FeedBack)
  • GCRY_CIPHER_MODE_CBC — режим сцепления блоков шифротекста. (Cipher Block Chaining)
  • GCRY_CIPHER_MODE_STREAM — режим для использования только со stream-алгоримами. К примеру, GCRY_CIPHER_MODE_STREAM.
  • GCRY_CIPHER_MODE_OFB — режим обратной связи вывода. (Output FeedBack)
  • GCRY_CIPHER_MODE_CTR — режим счетчика. (Counter)

flags — может быть 0 (нулём), либо комбинацией следующих флагов:
  • GCRY_CIPHER_SECURE — все операции расположены в защищенной памяти.
  • GCRY_CIPHER_ENABLE_SYNC — этот флаг включает режим CFB синхронизации.
  • GCRY_CIPHER_CBC_CTS — включает CTS (cipher text stealing) для режима CBC. (Флан не может быть использован одновременно с GCRY_CIPHER_CBC_MAC.) CTS режим позволяет производить изменения данных произвольной величины.
  • GCRY_CIPHER_CBC_MAC — подсчитать контрольную сумму CBC MAC keyed. (Флан не может быть использован одновременно с GCRY_CIPHER_CBC_CTS.)

Пример

gcryError = gcry_cipher_open(
	        &gcryCipherHd,
	        GCRY_CIPHER_AES128,
	        GCRY_CIPHER_MODE_CBC,
	        GCRY_CIPHER_CBC_CTS);

Для того чтобы можно было продолжить работу с дескриптором, в первую очередь нам надо установить ключ при помощи функции gcry_cipher_setkey.

gcry_error_t gcry_cipher_setkey (gcry_cipher_hd_t hd, const void *k, size_t l)

hd — полученный ранее дескриптор
k — ключ, он же — пароль (строка символов)
l — длина ключа (strlen(k))

Большинство режимов шифрования требуют вектор инициализации, которым обычно является не секретная случайная строка, исполняющая обязанности “соли” (salt). В случае CTR режима необходимо указывать счетчик, который так же схож со значением “соли”. Чтобы установить эти значения используем функции:
  • gcry_cipher_setiv (gcry_cipher_hd_t h, const void *k, size_t l)
    Установить вектор инициализации используемый для шифрования или де-шифрования. Вектор передается как буфер k длинны l байт и копируется во внутреннюю структуру данных. Функция так же проверяет соответствует ли вектор необходимым требованиям для заданного алгоритма (algo) и режима (mode).
  • gcry_cipher_setctr (gcry_cipher_hd_t h, const void *c, size_t l)
    Установить ветор-счетчик используемый для шифрования или де-шифрования. Счетчик передается как буфер k длинны l байт и копируется во внутреннюю структуру данных. Функция так же проверяет соответствует ли вектор необходимым требованиям для заданного алгоритма (т.е. вектор должен быть такого размера как и размер блока).

Вот мы и подошли к ключевому моменту — шифрование. Сам процесс шифрования выполняется функцией gcry_cipher_encrypt.

gcry_error_t gcry_cipher_encrypt (gcry_cipher_hd_t h, unsigned char *out, size_t outsize, const unsigned char *in, size_t inlen)

Функция может работать как с одним так и с двумя буферами. Если значение in передается как NULL и inlen равен 0 (нулю), то производится шифрование с одним буфером. Проще говоря буфер out, в котором перед вызовом функции содержит не шифрованный текст, при выходе из функции будет переписан новым текстом, шифрованным. Если значение in передается как не NULL, то inlen байт шифруется и помещается в буфер out (который должен быть не меньше размера inlen). outsize должен отображать размерность выделенного куска памяти для буфера out, чтобы функция могла проверить достаточно ли места для вывода. (Перекрытие буферов неразрешается.)

В зависимости от выбранного алгоритма и режима шифрования длина буферов должна быть кратной размеру блока.

В случае успешного шифрования код возврата — 0 (нуль). Иначе возвращается код ошибки.

Для освобождения памяти и дескриптора используйте функцию gcry_cipher_close.

void gcry_cipher_close (gcry_cipher_hd_t h)

Эта функция освободит контекст созданный во время выполнения gcry_cipher_open. Так же функция затирает нулями всю уязвимую информацию, которая была создана в рамках дескриптора h.

Процесс де-шифрования


Процесс де-шифрования схож с процессом шифрования функциями, которые необходимо вызвать. А именно (указаны в порядке использования):
  1. gcry_cipher_open — создаем дескриптор контекста
  2. gcry_cipher_setkey — задаем пароль
  3. gcry_cipher_setiv — задаем вектор инициализации
  4. gcry_cipher_decrypt — функция де-шифрования текста
  5. gcry_cipher_close — закрытие дескриптора контекста

Так как все функции (кроме gcry_cipher_decrypt) схожи, рассмотрим только саму функцию де-шифровки.

gcry_error_t gcry_cipher_decrypt (gcry_cipher_hd_t h, unsigned char *out, size_t outsize, const unsigned char *in, size_t inlen)

h — дескриптор контекста
out — буфер куда будет помещен результирующий (расшифрованный) текст
outsize — размер выделенной памяти для буфера out
in — шифрованный текст
inlen — размер шифрованного текста

Так же как и функция шифрования, функция де-шифрования может обойтись одним буфером. Для этого необходимо передать нули вместо in и inlen. В случае же, если эти параметры не нулевые, то inlen байт расшифровывается и кладется в буфер out, который должен быть размером по меньшей мере равно inlen. outsize должен быть установлен в значение, равное количеству байт выделенных для буфера out, чтобы функция могла убедиться в достаточности места для результата. (Перекрытие буфером не допускается.)

В зависимости от выбранного алгоритма и режима шифрования длина буферов должна быть кратна размеру блока.

В случае успеха функция возвращает 0. Иначе возвращается код ошибки.

Наглядный пример

#include <stdio.h>
#include <gcrypt.h>

#define ENCR 1
#define DECR 0

void myCrypt(int encdec, const char * pass, const char * salt, const char * text) {
  gcry_error_t     gcryError;
  gcry_cipher_hd_t hd;
  size_t           i;
  
  size_t passLength = strlen(pass);
  size_t saltLength = strlen(salt);
  size_t textLength = strlen(text)+encdec;
  char  * outBuffer = (char*)malloc(textLength);
  
  printf("%scryption...\n", encdec?"En":"De");
  printf("passLength = %d\n", passLength);
  printf("saltLength = %d\n", saltLength);
  printf("textLength = %d\n", textLength);
  printf("      pass = %s\n", pass);
  printf("      salt = %s\n", salt);
  printf("      text = %s\n", encdec?text:"<null>");
  
  // используем алгоритм шифрования - GCRY_CIPHER_AES128
  // используем режим сцепления блоков шифротекста
  // используем флаг GCRY_CIPHER_CBC_CTS, чтобы можно было шифровать текст любой длины
  gcryError = gcry_cipher_open(&hd, 
			       GCRY_CIPHER_AES128, 
			       GCRY_CIPHER_MODE_CBC, 
			       GCRY_CIPHER_CBC_CTS);
  
  if (gcryError) {
      printf("gcry_cipher_open failed:  %s/%s\n", 
	     gcry_strsource(gcryError), gcry_strerror(gcryError));
      return;
  }

  gcryError = gcry_cipher_setkey(hd, pass, passLength);
  if (gcryError) {
      printf("gcry_cipher_setkey failed:  %s/%s\n", 
	     gcry_strsource(gcryError), gcry_strerror(gcryError));
      return;
  }

  gcryError = gcry_cipher_setiv(hd, salt, saltLength);
  if (gcryError) {
      printf("gcry_cipher_setiv failed:  %s/%s\n", 
	     gcry_strsource(gcryError),gcry_strerror(gcryError));
      return;
  }

  switch (encdec) {
    case ENCR:
      gcryError = gcry_cipher_encrypt(hd, outBuffer, textLength, text, textLength);
      break;
    case DECR:
      gcryError = gcry_cipher_decrypt(hd, outBuffer, textLength, text, textLength);
  }
  if (gcryError) {
      printf("gcry_cipher_encrypt failed:  %s/%s\n", 
	     gcry_strsource(gcryError), gcry_strerror(gcryError));
      return;
  }
  
  switch (encdec) {
    case ENCR:
      printf("Ecnrypted text = ");
      for (i = 0; i<textLength; i++) printf("%02X", (unsigned char)outBuffer[i]);
      printf("\n");
      break;
    case DECR:
      printf("Original text = %s\n", outBuffer);
  }

  gcry_cipher_close(hd);
  free(outBuffer);
}

int main(int argc, char **argv) {
  if ( argc != 4 ) {
    printf("usage: %s <-e|-d> \"<password>\" \"<salt>\"\n", argv[0]);
    return 1;
  }
  
  int encdec = ENCR;
  char line[1024];
  
  printf("Enter text: ");
  fgets(line, sizeof(line), stdin);
  
  if ( !strcmp(argv[1], "-d") ) {
    // Преобразуем строку из 16чного представления в символьный
    int i = 0; char a[3] = {"00"};
    for (; i<strlen(line); i+=2) {
      sprintf(a, "%c%c", line[i], line[i+1]);
      line[i/2] = strtol(a, NULL, 16);
    }
    line[i/2-1] = '\0';
    encdec = DECR;
  }
  
  myCrypt(encdec, argv[2], argv[3], line);
  
  return 0;
}

Компилируем под Linux

gcc -o crypto main.c -lgcrypt

Запускаем

[serge@magnum enc]$ ./crypto -e "This's my passwd" "It is kinda salt"
Enter text: этот текст необходимо зашифровать
Encryption...
passLength = 16
saltLength = 16
textLength = 65
pass = This's my passwd
salt = It is kinda salt
text = этот текст необходимо зашифровать
Ecnrypted text = 7DA4C2CB7088BC7432E243B1B1ACAE2A4301CE92D5884404B5AFF181EC4C1B17D3B0565FD82BD88D78916506048BA20E87FA5DDE39288FCC32CA3EF02647F7B140

[serge@magnum enc]$ ./crypto -d "This's my passwd" "It is kinda salt"
Enter text: 7DA4C2CB7088BC7432E243B1B1ACAE2A4301CE92D5884404B5AFF181EC4C1B17D3B0565FD82BD88D78916506048BA20E87FA5DDE39288FCC32CA3EF02647F7B140
Decryption...
passLength = 16
saltLength = 16
textLength = 65
pass = This's my passwd
salt = It is kinda salt
text =
Original text = этот текст необходимо зашифровать


Более детальное описание библиотеки и интерфейсных функций можно найти по адресу: http://www.gnupg.org/documentation/manuals/gcrypt
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 12

    0
    Мне кажется это ближе к криптографии, а не к программированию.
    (На вопрос о том, как размещать посты сразу в двух блогах, если они находятся на стыке, ТМ ответили, что они в процессе, но красивого решения пока не нашли).
      0
      Почему же, это вполне обычное программирование. Вот буквально только сегодня ночью дописывал программу для AppStore — проверка талончика из магазина как раз требует немного «криптографии» для проверки хешей и подписей. Мне незачем вдаваться в подробности используемых стандартов, я взял libcrypto (OpenSSL), нашел нужные мне функции и просто реализовал проверку без задних мыслей, собственно словами автора топика — «не вникая в то как работает выбранный вами алгоритм».
      +3
      Скажите, почему не воспользовались OpenSSL?
        0
        Или Crypto++ или PolarSSL?
          0
          libcrypto была первой библиотекой, попавшейся под руку.
        0
        Отлично написанная статья, подробно, достаточно и с примером. Хорошая работа, спасибо!
        0
        В myCrypt при любой ошибке перед return; надо бы добавить free(outBuffer);
          0
          Да, согласен с вами.
          0
          А нет ни у кого бинарников этой либы под винду? Вот ни в какую собрать не могу ни студией ни mingw ни msys ни кросскомпиляция из под линукса, но я видел у некоторых прог gcrypt.dll и значит собирают как то!
            0
            Почему де-шифровки?
            Два различных понятия используются в криптологии с применением терминов — расшифрование, когда ключ шифртекста доступен, и термин — дешифрование (т.е. взлом, когда ключ недоступен).

            Only users with full accounts can post comments. Log in, please.