From 82c6d450a695da9f6b409cb1dd1fb631b804a992 Mon Sep 17 00:00:00 2001
From: Axel Kenzo <axelkenzo@mail.ru>
Date: Sat, 27 Jun 2020 17:36:52 +0300
Subject: [PATCH] =?UTF-8?q?=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD?=
 =?UTF-8?q?=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B5=D0=B0=D0=BB?=
 =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B6=D0=B8?=
 =?UTF-8?q?=D0=BC=D0=B0=20acpkm=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?=
 =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?=
 =?UTF-8?q?=D1=8F=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8?=
 =?UTF-8?q?=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B2=20pem-?=
 =?UTF-8?q?=D1=84=D0=B0=D0=B9=D0=BB=D0=B5=20=D0=98=D1=81=D0=BF=D1=80=D0=B0?=
 =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8?=
 =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC?=
 =?UTF-8?q?=D0=B0=20cfb?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CMakeLists.txt            |   1 +
 Changelog.md              |   2 +
 TODO                      |   4 +-
 aktool/Readme.md          |  56 +++---
 aktool/aktool.1           |  56 +++---
 aktool/aktool.template.in |   3 +-
 doc/Doxyfile.in           |   8 +-
 source/ak_acpkm.c         | 370 ++++++++++++++++++++++++++++++++++++++
 source/ak_asn1.c          |  76 ++++----
 source/ak_bckey.c         | 140 ++++++++++++++-
 source/ak_bckey.h         |  19 +-
 source/ak_kuznechik.c     | 122 ++++++++++++-
 source/ak_libakrypt.c     |  10 +-
 source/ak_magma.c         | 133 +++++++++++++-
 source/ak_sign.c          |   4 +-
 source/ak_tools.c         |  30 ++++
 source/ak_tools.h         |   4 +-
 tests/test-asn1-build.c   |   2 +
 tests/test-bckey04.c      |  32 +++-
 tests/test-bckey05-cfb.c  |   4 +-
 20 files changed, 939 insertions(+), 137 deletions(-)
 create mode 100644 source/ak_acpkm.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c2e38c3b..71eec3a4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -109,6 +109,7 @@ if( LIBAKRYPT_CRYPTO_FUNCTIONS )
     source/ak_bckey.c
     source/ak_magma.c
     source/ak_kuznechik.c
+    source/ak_acpkm.c
     source/ak_context_manager.c
     source/ak_sign.c
     source/ak_asn1_keys.c
diff --git a/Changelog.md b/Changelog.md
index 35ddebf0..2dedfe06 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -4,6 +4,8 @@
 ## Изменения в версии 0.8.5
 
  - Изменена внутренняя структура ak_oid и реализация функций поиска идентификаторов
+ - Восстановлена реализация потеряных когда-то за ненадобностью режимов шифрования ofb, cfb и acpkm
+ - Изменена реализация функции сохранения файлов в pem-формате
  - В утилиту aktool добавлен ряд функций тестирования работоспособности библиотеки
    и скорости выполнения криптографических алгоритмов
 
diff --git a/TODO b/TODO
index bbbf16a2..81edd052 100644
--- a/TODO
+++ b/TODO
@@ -1,11 +1,13 @@
  План работ до завершения версии 0.8
 
+  вернуть handle
+
   режимы шифрования
    - вернуть acpkm
 
   режимы имитозащиты
-   - сделать acpkm-omac
    - сделать сmac (как отдельный ключ имитовставки)
+   - сделать acpkm-omac
    - сделать acpkm-omac  (как отдельный ключ имитовставки)
 
   режимы aead
diff --git a/aktool/Readme.md b/aktool/Readme.md
index 41890c04..df58a5af 100644
--- a/aktool/Readme.md
+++ b/aktool/Readme.md
@@ -551,7 +551,7 @@ der-последовательностей, закодированных в ко
 
 \-o, \--output \<файл\>
 : Опция позволяет в явном виде определить
-имя файла в который будут помещены конверированные или объединенные данные.
+имя файла в который будут помещены конвертированные или объединенные данные.
 Данную опцию имеет смысл использовать совместно с опциями `--convert` и `--join`.
 
 \--to \<формат\>
@@ -591,33 +591,33 @@ der-последовательностей, закодированных в ко
 
 
     в”ЊSEQUENCEв”ђ
-         в”њSEQUENCEв”ђ
-         в”‚        в”њINTEGER 0x0
-         в”‚        в”њSEQUENCEв”ђ
-         в”‚        в”‚        в””SETв”ђ
-         в”‚        в”‚            в””SEQUENCEв”ђ
-         в”‚        в”‚                     в”њOBJECT IDENTIFIER 2.5.4.3
-         в”‚        в”‚                     в””PRINTABLE STRING Example
-         в”‚        в”њSEQUENCEв”ђ
-         в”‚        в”‚        в”њSEQUENCEв”ђ
-         в”‚        в”‚        в”‚        в”њOBJECT IDENTIFIER 1.2.643.7.1.1.1.1
-         в”‚        в”‚        в”‚        в””SEQUENCEв”ђ
-         в”‚        в”‚        в”‚                 в””OBJECT IDENTIFIER 1.2.643.7.1.2.1.1.1
-         в”‚        в”‚        в””BIT STRING
-         в”‚        в”‚           04 40 74 27 95 D4 BE E8 84 DD F2 85 0F EC 03 EA
-         в”‚        в”‚           3F AF 18 44 E0 1D 9D A6 0B 64 50 93 A5 5E 26 DF
-         в”‚        в”‚           C3 99 78 F5 96 CF 4D 4D 0C 6C F1 D1 89 43 D9 44
-         в”‚        в”‚           93 D1 6B 9E C0 A1 6D 51 2D 2E 12 7C C4 69 1A 63
-         в”‚        в”‚           18 E2
-         в”‚        в””[0]в”ђ
-         в”‚             (null)
-         в”њSEQUENCEв”ђ
-         в”‚        в””OBJECT IDENTIFIER 1.2.643.7.1.1.3.2
-         в””BIT STRING
-            1B DC 2A 13 17 67 9B 66 23 2F 63 EA 16 FF 7C 64
-            CC AA B9 AD 85 5F C6 E1 80 91 66 1D B7 9D 48 12
-            1D 0E 1D A5 BE 34 7C 6F 1B 52 56 C7 AE AC 20 0A
-            D6 4A C7 7A 6F 5B 3A 0E 09 73 18 E7 AE 6E E7 69
+             в”њSEQUENCEв”ђ
+             в”‚        в”њINTEGER 0x0
+             в”‚        в”њSEQUENCEв”ђ
+             в”‚        в”‚        в””SETв”ђ
+             в”‚        в”‚            в””SEQUENCEв”ђ
+             в”‚        в”‚                     в”њOBJECT IDENTIFIER 2.5.4.3
+             в”‚        в”‚                     в””PRINTABLE STRING Example
+             в”‚        в”њSEQUENCEв”ђ
+             в”‚        в”‚        в”њSEQUENCEв”ђ
+             в”‚        в”‚        в”‚        в”њOBJECT IDENTIFIER 1.2.643.7.1.1.1.1
+             в”‚        в”‚        в”‚        в””SEQUENCEв”ђ
+             в”‚        в”‚        в”‚                 в””OBJECT IDENTIFIER 1.2.643.7.1.2.1.1.1
+             в”‚        в”‚        в””BIT STRING
+             в”‚        в”‚           04 40 74 27 95 D4 BE E8 84 DD F2 85 0F EC 03 EA
+             в”‚        в”‚           3F AF 18 44 E0 1D 9D A6 0B 64 50 93 A5 5E 26 DF
+             в”‚        в”‚           C3 99 78 F5 96 CF 4D 4D 0C 6C F1 D1 89 43 D9 44
+             в”‚        в”‚           93 D1 6B 9E C0 A1 6D 51 2D 2E 12 7C C4 69 1A 63
+             в”‚        в”‚           18 E2
+             в”‚        в””[0]в”ђ
+             в”‚             (null)
+             в”њSEQUENCEв”ђ
+             в”‚        в””OBJECT IDENTIFIER 1.2.643.7.1.1.3.2
+             в””BIT STRING
+               1B DC 2A 13 17 67 9B 66 23 2F 63 EA 16 FF 7C 64
+               CC AA B9 AD 85 5F C6 E1 80 91 66 1D B7 9D 48 12
+               1D 0E 1D A5 BE 34 7C 6F 1B 52 56 C7 AE AC 20 0A
+               D6 4A C7 7A 6F 5B 3A 0E 09 73 18 E7 AE 6E E7 69
 
 
 Следующий вызов демонстрирует
diff --git a/aktool/aktool.1 b/aktool/aktool.1
index 1f4558ae..42d9c9ae 100644
--- a/aktool/aktool.1
+++ b/aktool/aktool.1
@@ -1996,7 +1996,7 @@ ASN.1 \[u0434]\[u0435]\[u0440]\[u0435]\[u0432]\[u043E] \[u043D]\[u0430]
 \[u0432] \[u043A]\[u043E]\[u0442]\[u043E]\[u0440]\[u044B]\[u0439]
 \[u0431]\[u0443]\[u0434]\[u0443]\[u0442]
 \[u043F]\[u043E]\[u043C]\[u0435]\[u0449]\[u0435]\[u043D]\[u044B]
-\[u043A]\[u043E]\[u043D]\[u0432]\[u0435]\[u0440]\[u0438]\[u0440]\[u043E]\[u0432]\[u0430]\[u043D]\[u043D]\[u044B]\[u0435]
+\[u043A]\[u043E]\[u043D]\[u0432]\[u0435]\[u0440]\[u0442]\[u0438]\[u0440]\[u043E]\[u0432]\[u0430]\[u043D]\[u043D]\[u044B]\[u0435]
 \[u0438]\[u043B]\[u0438]
 \[u043E]\[u0431]\[u044A]\[u0435]\[u0434]\[u0438]\[u043D]\[u0435]\[u043D]\[u043D]\[u044B]\[u0435]
 \[u0434]\[u0430]\[u043D]\[u043D]\[u044B]\[u0435].
@@ -2122,33 +2122,33 @@ aktool a request_edw.pem
 .nf
 \f[C]
 \[u250C]SEQUENCE\[u2510]
-     \[u251C]SEQUENCE\[u2510]
-     \[br]        \[u251C]INTEGER 0x0
-     \[br]        \[u251C]SEQUENCE\[u2510]
-     \[br]        \[br]        \[u2514]SET\[u2510]
-     \[br]        \[br]            \[u2514]SEQUENCE\[u2510]
-     \[br]        \[br]                     \[u251C]OBJECT IDENTIFIER 2.5.4.3
-     \[br]        \[br]                     \[u2514]PRINTABLE STRING Example
-     \[br]        \[u251C]SEQUENCE\[u2510]
-     \[br]        \[br]        \[u251C]SEQUENCE\[u2510]
-     \[br]        \[br]        \[br]        \[u251C]OBJECT IDENTIFIER 1.2.643.7.1.1.1.1
-     \[br]        \[br]        \[br]        \[u2514]SEQUENCE\[u2510]
-     \[br]        \[br]        \[br]                 \[u2514]OBJECT IDENTIFIER 1.2.643.7.1.2.1.1.1
-     \[br]        \[br]        \[u2514]BIT STRING
-     \[br]        \[br]           04 40 74 27 95 D4 BE E8 84 DD F2 85 0F EC 03 EA
-     \[br]        \[br]           3F AF 18 44 E0 1D 9D A6 0B 64 50 93 A5 5E 26 DF
-     \[br]        \[br]           C3 99 78 F5 96 CF 4D 4D 0C 6C F1 D1 89 43 D9 44
-     \[br]        \[br]           93 D1 6B 9E C0 A1 6D 51 2D 2E 12 7C C4 69 1A 63
-     \[br]        \[br]           18 E2
-     \[br]        \[u2514][0]\[u2510]
-     \[br]             (null)
-     \[u251C]SEQUENCE\[u2510]
-     \[br]        \[u2514]OBJECT IDENTIFIER 1.2.643.7.1.1.3.2
-     \[u2514]BIT STRING
-        1B DC 2A 13 17 67 9B 66 23 2F 63 EA 16 FF 7C 64
-        CC AA B9 AD 85 5F C6 E1 80 91 66 1D B7 9D 48 12
-        1D 0E 1D A5 BE 34 7C 6F 1B 52 56 C7 AE AC 20 0A
-        D6 4A C7 7A 6F 5B 3A 0E 09 73 18 E7 AE 6E E7 69
+         \[u251C]SEQUENCE\[u2510]
+         \[br]        \[u251C]INTEGER 0x0
+         \[br]        \[u251C]SEQUENCE\[u2510]
+         \[br]        \[br]        \[u2514]SET\[u2510]
+         \[br]        \[br]            \[u2514]SEQUENCE\[u2510]
+         \[br]        \[br]                     \[u251C]OBJECT IDENTIFIER 2.5.4.3
+         \[br]        \[br]                     \[u2514]PRINTABLE STRING Example
+         \[br]        \[u251C]SEQUENCE\[u2510]
+         \[br]        \[br]        \[u251C]SEQUENCE\[u2510]
+         \[br]        \[br]        \[br]        \[u251C]OBJECT IDENTIFIER 1.2.643.7.1.1.1.1
+         \[br]        \[br]        \[br]        \[u2514]SEQUENCE\[u2510]
+         \[br]        \[br]        \[br]                 \[u2514]OBJECT IDENTIFIER 1.2.643.7.1.2.1.1.1
+         \[br]        \[br]        \[u2514]BIT STRING
+         \[br]        \[br]           04 40 74 27 95 D4 BE E8 84 DD F2 85 0F EC 03 EA
+         \[br]        \[br]           3F AF 18 44 E0 1D 9D A6 0B 64 50 93 A5 5E 26 DF
+         \[br]        \[br]           C3 99 78 F5 96 CF 4D 4D 0C 6C F1 D1 89 43 D9 44
+         \[br]        \[br]           93 D1 6B 9E C0 A1 6D 51 2D 2E 12 7C C4 69 1A 63
+         \[br]        \[br]           18 E2
+         \[br]        \[u2514][0]\[u2510]
+         \[br]             (null)
+         \[u251C]SEQUENCE\[u2510]
+         \[br]        \[u2514]OBJECT IDENTIFIER 1.2.643.7.1.1.3.2
+         \[u2514]BIT STRING
+           1B DC 2A 13 17 67 9B 66 23 2F 63 EA 16 FF 7C 64
+           CC AA B9 AD 85 5F C6 E1 80 91 66 1D B7 9D 48 12
+           1D 0E 1D A5 BE 34 7C 6F 1B 52 56 C7 AE AC 20 0A
+           D6 4A C7 7A 6F 5B 3A 0E 09 73 18 E7 AE 6E E7 69
 \f[R]
 .fi
 .PP
diff --git a/aktool/aktool.template.in b/aktool/aktool.template.in
index d8bb304b..cb894313 100644
--- a/aktool/aktool.template.in
+++ b/aktool/aktool.template.in
@@ -14,7 +14,6 @@
 \setsansfont{CMU Sans Serif}
 \setmonofont{CMU Typewriter Text}
 
-
 % ---------------------------------------------------------------------------------------------- %
 %
 \providecommand{\tightlist}{%
@@ -52,7 +51,7 @@ $endif$
 {\bfseries\textsc{Инструкция по применению}}\\[1\baselineskip]
 
 \vspace{0.5\textheight}
-{\small\textsc{Axel Kenzo \& The Company Of Belles Lettres~~\copyright~~2014 -- 2020}}\\[1\baselineskip]
+{\small\textsc{Axel Kenzo и <<Компания изящной словесности>>~~\copyright~~2020}}\\[1\baselineskip]
 
 } % end vbox
 } % end parbox
diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 41cdb4fc..06adfb0b 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -2076,7 +2076,7 @@ XML_NS_MEMB_FILE_SCOPE = NO
 # that can be used to generate PDF.
 # The default value is: NO.
 
-GENERATE_DOCBOOK       = NO
+GENERATE_DOCBOOK       = YES
 
 # The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
 # If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
@@ -2419,7 +2419,7 @@ INCLUDED_BY_GRAPH      = YES
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALL_GRAPH             = NO
+CALL_GRAPH             = YES
 
 # If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
 # dependency graph for every global function or class method.
@@ -2431,7 +2431,7 @@ CALL_GRAPH             = NO
 # The default value is: NO.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-CALLER_GRAPH           = NO
+CALLER_GRAPH           = YES
 
 # If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
 # hierarchy of all classes instead of a textual one.
@@ -2531,7 +2531,7 @@ PLANTUML_INCLUDE_PATH  =
 # Minimum value: 0, maximum value: 10000, default value: 50.
 # This tag requires that the tag HAVE_DOT is set to YES.
 
-DOT_GRAPH_MAX_NODES    = 50
+DOT_GRAPH_MAX_NODES    = 25
 
 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
 # generated by dot. A depth value of 3 means that only nodes reachable from the
diff --git a/source/ak_acpkm.c b/source/ak_acpkm.c
new file mode 100644
index 00000000..788ecc75
--- /dev/null
+++ b/source/ak_acpkm.c
@@ -0,0 +1,370 @@
+/* ----------------------------------------------------------------------------------------------- */
+/*  Copyright (c) 2019 by Petr Mikhalitsyn, myprettycapybara@gmail.com                             */
+/*                        Axel Kenzo, axelkenzo@mail.ru                                            */
+/*                                                                                                 */
+/*  Файл ak_acpkm.h                                                                                */
+/*  - содержит реализацию криптографических алгоритмов семейства ACPKM из Р 1323565.1.017—2018     */
+/* ----------------------------------------------------------------------------------------------- */
+ #include <ak_tools.h>
+ #include <ak_bckey.h>
+
+/* ----------------------------------------------------------------------------------------------- */
+/*! \details Функция вычисляет новое значение секретного ключа в соответствии с соотношениями
+    из раздела 4.1, см. Р 1323565.1.017—2018.
+    После выработки новое значение помещается вместо старого.
+    Одновременно, изменяется ресурс нового ключа: его тип принимает значение - \ref key_using_resource,
+    а счетчик принимает значение, определяемое одной из опций
+
+     - `ackpm_section_magma_block_count`,
+     - `ackpm_section_kuznechik_block_count`.
+
+    @param bkey Контекст ключа алгоритма блочного шифрования, для которого вычисляется
+    новое значение. Контекст должен быть инициализирован и содержать ключевое значение.
+    @return В случае возникновения ошибки функция возвращает ее код, в противном случае
+    возвращается \ref ak_error_ok (ноль)                                                           */
+/* ----------------------------------------------------------------------------------------------- */
+ int ak_bckey_context_next_acpkm_key( ak_bckey bkey )
+{
+  ssize_t counter = 0;
+  int error = ak_error_ok;
+  ak_uint8 new_key[32], acpkm[32] = {
+     0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, 0x93, 0x92, 0x91, 0x90,
+     0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80 };
+
+ /* проверки */
+  if( bkey == NULL ) return ak_error_message( ak_error_null_pointer, __func__,
+                                                        "using null pointer to block cipher key" );
+  if( bkey->key.key_size != 32 ) return ak_error_message_fmt( ak_error_wrong_length, __func__,
+                                 "using block cipher key with unexpected length %u", bkey->bsize );
+ /* целостность ключа */
+  if( bkey->key.check_icode( &bkey->key ) != ak_true )
+    return ak_error_message( ak_error_wrong_key_icode,
+                                        __func__, "incorrect integrity code of secret key value" );
+ /* выработка нового значения */
+   switch( bkey->bsize ) {
+      case  8: /* шифр с длиной блока 64 бита */
+         bkey->encrypt( &bkey->key, acpkm, new_key );
+         bkey->encrypt( &bkey->key, acpkm +8, new_key +8 );
+         bkey->encrypt( &bkey->key, acpkm +16, new_key +16 );
+         bkey->encrypt( &bkey->key, acpkm +24, new_key +24 );
+         counter = ak_libakrypt_get_option( "acpkm_section_magma_block_count" );
+         break;
+      case 16: /* шифр с длиной блока 128 бит */
+         bkey->encrypt( &bkey->key, acpkm, new_key );
+         bkey->encrypt( &bkey->key, acpkm +16, new_key +16 );
+         counter = ak_libakrypt_get_option( "acpkm_section_kuznechik_block_count" );
+         break;
+      default: return ak_error_message( ak_error_wrong_block_cipher,
+                                           __func__ , "incorrect block size of block cipher key" );
+   }
+
+ /* присваиваем ключу значение */
+  if(( error = ak_bckey_context_set_key( bkey, new_key, bkey->key.key_size )) != ak_error_ok )
+    ak_error_message( error, __func__ , "can't replace key by new using acpkm" );
+   else {
+           bkey->key.resource.value.type = key_using_resource;
+           bkey->key.resource.value.counter = counter;
+        }
+  ak_ptr_context_wipe( new_key, sizeof( new_key ), &bkey->key.generator );
+ return error;
+}
+
+/* ----------------------------------------------------------------------------------------------- */
+#ifdef LIBAKRYPT_LITTLE_ENDIAN
+  #define acpkm_block64 {\
+              nkey.encrypt( &nkey.key, ctr, yaout );\
+              ctr[0] += 1;\
+              ((ak_uint64 *) outptr)[0] = yaout[0] ^ ((ak_uint64 *) inptr)[0];\
+              outptr++; inptr++;\
+           }
+
+  #define acpkm_block128 {\
+              nkey.encrypt( &nkey.key, ctr, yaout );\
+              if(( ctr[0] += 1 ) == 0 ) ctr[1]++;\
+              ((ak_uint64 *) outptr)[0] = yaout[0] ^ ((ak_uint64 *) inptr)[0];\
+              ((ak_uint64 *) outptr)[1] = yaout[1] ^ ((ak_uint64 *) inptr)[1];\
+              outptr += 2; inptr += 2;\
+           }
+
+#else
+  #define acpkm_block64 {\
+              nkey.encrypt( &nkey.key, ctr, yaout );\
+              ctr[0] = bswap_64( ctr[0] ); ctr[0] += 1; ctr[0] = bswap_64( ctr[0] );\
+              ((ak_uint64 *) outptr)[0] = yaout[0] ^ ((ak_uint64 *) inptr)[0];\
+              outptr++; inptr++;\
+           }
+
+  #define acpkm_block128 {\
+              nkey.encrypt( &nkey.key, ctr, yaout );\
+              ctr[0] = bswap_64( ctr[0] ); ctr[0] += 1; ctr[0] = bswap_64( ctr[0] );\
+              if( ctr[0] == 0 ) { \
+                ctr[1] = bswap_64( ctr[0] ); ctr[1] += 1; ctr[1] = bswap_64( ctr[0] );\
+              }\
+              ((ak_uint64 *) outptr)[0] = yaout[0] ^ ((ak_uint64 *) inptr)[0];\
+              ((ak_uint64 *) outptr)[1] = yaout[1] ^ ((ak_uint64 *) inptr)[1];\
+              outptr += 2; inptr += 2;\
+           }
+#endif
+
+/* ----------------------------------------------------------------------------------------------- */
+/*! В режиме ACPKM для шифрования используется операция гаммирования - операция сложения
+    открытого (зашифровываемого) текста с гаммой, вырабатываемой шифром, по модулю два.
+    Поэтому, для зашифрования и расшифрования информациии используется одна и та же функция.
+
+    В процессе шифрования исходные данные разбиваются на секции фиксированной длины, после чего
+    каждая секция шифруется на своем ключе. Длина секции является параметром алгоритма и
+    не должна превосходить величины, определяемой одной из следующих технических характеристик
+    (опций)
+
+     - `ackpm_section_magma_block_count`,
+     - `ackpm_section_kuznechik_block_count`.
+
+    Значение синхропосылки `iv` копируется во временную область памяти и, в ходе выполнения
+    функции, не изменяется. Повторный вызов функции ak_bckey_context_ctr_acpkm() с нулевым
+    указатетем на синхропосылу, как в случае функции ak_bckey_context_ctr(), не допускается.
+
+    @param bkey Контекст ключа алгоритма блочного шифрования,
+    используемый для шифрования и порождения цепочки производных ключей.
+    @param in Указатель на область памяти, где хранятся входные
+    (зашифровываемые/расшифровываемые) данные
+    @param out Указатель на область памяти, куда помещаются выходные
+    (расшифровываемые/зашифровываемые) данные; этот указатель может совпадать с in
+    @param size Размер зашировываемых данных (в байтах). Длина зашифровываемых данных может
+    принимать любое значение, не превосходящее \f$ 2^{\frac{8n}{2}-1}\f$, где \f$ n \f$
+    длина блока алгоритма шифрования (8 или 16 байт).
+
+    @param section_size Размер одной секции в байтах. Данная величина должна быть кратна длине блока
+    используемого алгоритма шифрования.
+
+    @param iv имитовставка
+    @param iv_size длина имитовставки (в байтах)
+
+    @return В случае возникновения ошибки функция возвращает ее код, в противном случае
+    возвращается \ref ak_error_ok (ноль)                                                           */
+/* ----------------------------------------------------------------------------------------------- */
+ int ak_bckey_context_ctr_acpkm( ak_bckey bkey, ak_pointer in, ak_pointer out, size_t size,
+                                                 size_t section_size, ak_pointer iv, size_t iv_size)
+{
+  struct bckey nkey;
+  int error = ak_error_ok;
+  ssize_t j = 0, sections = 0, tail = 0, seclen = 0, maxseclen = 0, mcount = 0;
+  ak_uint64 yaout[2], *inptr = (ak_uint64 *)in, *outptr = (ak_uint64 *)out, ctr[2] = { 0, 0 };
+
+ /* выполняем проверку размера входных данных */
+  if( section_size%bkey->bsize != 0 )
+    return ak_error_message( ak_error_wrong_block_cipher_length,
+                               __func__ , "the length of section is not divided by block length" );
+ /* проверяем целостность ключа */
+  if( bkey->key.check_icode( &bkey->key ) != ak_true )
+    return ak_error_message( ak_error_wrong_key_icode, __func__,
+                                                  "incorrect integrity code of secret key value" );
+ /* проверяем размер синхропосылки */
+  if( iv == NULL ) return ak_error_message( ak_error_null_pointer, __func__,
+                                                   "using null pointer to initialization vector" );
+  if( iv_size < ( bkey->bsize >> 1 ))
+    return ak_error_message( ak_error_wrong_block_cipher_length,
+                                   __func__ , "the length of initialization vector is incorrect" );
+
+ /* получаем максимально возможную длину секции, количество сообщений на одном ключе,
+                                                             а также устанавливаем синхропосылку */
+  switch( bkey->bsize ) {
+    case 8:
+       maxseclen = ak_libakrypt_get_option( "acpkm_section_magma_block_count" );
+       mcount = ak_libakrypt_get_option( "magma_cipher_resource" )/maxseclen;
+       #ifdef LIBAKRYPT_LITTLE_ENDIAN
+         ctr[0] = ((ak_uint64 *)iv)[0] << 32;
+       #else
+         ctr[0] = ((ak_uint32 *)iv)[0];
+       #endif
+      break;
+    case 16:
+       maxseclen = ak_libakrypt_get_option( "acpkm_section_kuznechik_block_count" );
+       mcount = ak_libakrypt_get_option( "kuznechik_cipher_resource" )/maxseclen;
+       ctr[1] = ((ak_uint64 *) iv)[0];
+      break;
+    default: return ak_error_message( ak_error_wrong_block_cipher,
+                                           __func__ , "incorrect block size of block cipher key" );
+  }
+ /* проверяем, что пользователь определил длину секции не очень большим значением */
+  seclen = ( ssize_t )( section_size/bkey->bsize );
+  if( seclen > maxseclen ) return ak_error_message( ak_error_wrong_length, __func__,
+                                                                 "section has very large length" );
+ /* проверяем ресурс ключа перед использованием */
+  if( bkey->key.resource.value.type != key_using_resource ) { /* мы пришли сюда в первый раз */
+    bkey->key.resource.value.type = key_using_resource;
+    bkey->key.resource.value.counter = mcount; /* здесь находится максимальное число сообщений,
+                                                  которые могут быть зашифрованы на данном ключе */
+  } else {
+      if( bkey->key.resource.value.counter < 1 )
+        return ak_error_message( ak_error_low_key_resource,
+                                __func__ , "low key using resource for block cipher key context" );
+       else bkey->key.resource.value.counter--;
+     }
+
+ /* теперь размножаем исходный ключ */
+  if(( error = ak_bckey_context_create_and_set_bckey( &nkey, bkey )) != ak_error_ok )
+    return ak_error_message( error, __func__, "incorrect key duplication" );
+ /* и меняем ресурс для производного ключа */
+  nkey.key.resource.value.counter = maxseclen;
+
+ /* дальнейшие криптографические действия применяются к новому экземпляру ключа */
+  sections = ( ssize_t )( size/section_size );
+  tail = ( ssize_t )( size - ( size_t )( sections*seclen )*nkey.bsize );
+  if( sections > 0 ) {
+    do{
+       switch( nkey.bsize ) { /* обрабатываем одну секцию */
+         case 8: for( j = 0; j < seclen; j++ ) acpkm_block64; break;
+         case 16: for( j = 0; j < seclen; j++ ) acpkm_block128; break;
+         default: ak_error_message( ak_error_wrong_block_cipher,
+                                           __func__ , "incorrect block size of block cipher key" );
+       }
+      /* вычисляем следующий ключ */
+       if(( error = ak_bckey_context_next_acpkm_key( &nkey )) != ak_error_ok ) {
+         ak_error_message_fmt( error, __func__, "incorrect key generation after %u sections",
+                                                                         (unsigned int) sections );
+         goto labex;
+       }
+    } while( --sections > 0 );
+  } /* конец обработки случая, когда sections > 0 */
+
+  if( tail ) { /* теперь обрабатываем фрагмент данных, не кратный длине секции */
+    if(( seclen = tail/(ssize_t)( nkey.bsize )) > 0 ) {
+       switch( nkey.bsize ) { /* обрабатываем данные, кратные длине блока */
+         case 8: for( j = 0; j < seclen; j++ ) acpkm_block64; break;
+         case 16: for( j = 0; j < seclen; j++ ) acpkm_block128; break;
+         default: ak_error_message( ak_error_wrong_block_cipher,
+                                            __func__ , "incorrect block size of block cipher key" );
+       }
+    }
+  /* остался последний фрагмент, длина которого меньше длины блока
+                      в качестве гаммы мы используем старшие байты */
+    if(( tail -= seclen*(ssize_t)( nkey.bsize )) > 0 ) {
+      nkey.encrypt( &nkey.key, ctr, yaout );
+      for( j = 0; j < tail; j++ ) ((ak_uint8 *) outptr)[j] =
+                         ((ak_uint8 *)yaout)[(ssize_t)nkey.bsize-tail+j] ^ ((ak_uint8 *) inptr)[j];
+    }
+  }
+
+  labex: ak_bckey_context_destroy( &nkey );
+ return error;
+}
+
+/* ----------------------------------------------------------------------------------------------- */
+ bool_t ak_bckey_test_acpkm( void )
+{
+  struct bckey key;
+  int error = ak_error_ok, audit = ak_log_get_level();
+  ak_uint8 skey[32] = {
+    0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
+    0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88
+  };
+  ak_uint8 iv1[8] = { 0xf0, 0xce, 0xab, 0x90, 0x78, 0x56, 0x34, 0x12 };
+  ak_uint8 iv2[4] = { 0x78, 0x56, 0x34, 0x12 };
+
+  ak_uint8 out[112], in1[112] = {
+    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
+    0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00,
+    0x00, 0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
+    0x11, 0x00, 0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22,
+    0x22, 0x11, 0x00, 0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33,
+    0x33, 0x22, 0x11, 0x00, 0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44,
+    0x44, 0x33, 0x22, 0x11, 0x00, 0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55
+  };
+  ak_uint8 out1[112] = {
+    0xb8, 0xa1, 0xbd, 0x40, 0xa2, 0x5f, 0x7b, 0xd5, 0xdb, 0xd1, 0x0e, 0xc1, 0xbe, 0xd8, 0x95, 0xf1,
+    0xe4, 0xde, 0x45, 0x3c, 0xb3, 0xe4, 0x3c, 0xf3, 0x5d, 0x3e, 0xa1, 0xf6, 0x33, 0xe7, 0xee, 0x85,
+    0x00, 0xe8, 0x85, 0x5e, 0x27, 0x06, 0x17, 0x00, 0x55, 0x4c, 0x6f, 0x64, 0x8f, 0xeb, 0xce, 0x4b,
+    0x46, 0x50, 0x80, 0xd0, 0xaf, 0x34, 0x48, 0x3e, 0x39, 0x94, 0xd0, 0x68, 0xf5, 0x4d, 0x7c, 0x58,
+    0x6e, 0x89, 0x8a, 0x6b, 0x31, 0x6c, 0xfc, 0x1c, 0xe1, 0xec, 0xae, 0x86, 0x76, 0xf5, 0x30, 0xcf,
+    0x3e, 0x16, 0x23, 0x34, 0x74, 0x3b, 0x4f, 0x0c, 0x46, 0x36, 0x36, 0x81, 0xec, 0x07, 0xfd, 0xdf,
+    0x5d, 0xde, 0xd6, 0xfb, 0xe7, 0x21, 0xd2, 0x69, 0xd4, 0xc8, 0xfa, 0x82, 0xc2, 0xa9, 0x09, 0x64
+  };
+  ak_uint8 in2[56] = {
+    0x00, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, /* по сравнению с текстом рекомендаций открытый */
+    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, /* текст выведен в блоки по 8 байт и развернут  */
+    0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00, /* в обратном порядке, по аналогии со способом, */
+    0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99, 0x88, /* использованом в стандарте на блочные шифры   */
+    0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11,
+    0x00, 0x0a, 0xff, 0xee, 0xcc, 0xbb, 0xaa, 0x99,
+    0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22
+  };
+  ak_uint8 out2[56] = {
+    0xab, 0x4c, 0x1e, 0xeb, 0xee, 0x1d, 0xb8, 0x2a,
+    0xea, 0x94, 0x6b, 0xbd, 0xc4, 0x04, 0xe1, 0x68,
+    0x6b, 0x5b, 0x2e, 0x6c, 0xaf, 0x67, 0x2c, 0xc7,
+    0x2e, 0xb3, 0xf1, 0x70, 0x17, 0xb6, 0xaf, 0x0e,
+    0x82, 0x13, 0xed, 0x9e, 0x14, 0x71, 0xae, 0xa1,
+    0x6f, 0xec, 0x72, 0x06, 0x18, 0x67, 0xd4, 0xab,
+    0xc1, 0x72, 0xca, 0x3f, 0x5b, 0xf1, 0xa2, 0x84
+  };
+
+ /* 1. Выполняем тест для алгоритма Магма */
+  if(( error = ak_bckey_context_create_magma( &key )) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect creation of magma secret key" );
+    return ak_false;
+  }
+  if(( error = ak_bckey_context_set_key( &key, skey, sizeof( skey ))) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect assigning a key value" ); goto ex1; }
+
+  if(( error = ak_bckey_context_ctr_acpkm( &key, in2, out, sizeof( in2 ),
+                                                       16, iv2, sizeof( iv2 ))) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect encryption of plain text" ); goto ex1; }
+
+  if( memcmp( out, out2, sizeof( in2 )) != 0 ) {
+    ak_error_message( error = ak_error_not_equal_data, __func__,
+                "incorrect data comparizon after acpkm encryption with Magma cipher" ); goto ex1; }
+ /* расшифровываем */
+  if(( error = ak_bckey_context_ctr_acpkm( &key, out2, out, sizeof( in2 ),
+                                                       16, iv2, sizeof( iv2 ))) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect encryption of plain text" ); goto ex1; }
+
+  if( memcmp( out, in2, sizeof( in2 )) != 0 ) {
+    ak_error_message( error = ak_error_not_equal_data, __func__,
+                "incorrect data comparizon after acpkm decryption with Magma cipher" ); goto ex1; }
+
+  if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
+                                                    "encryption/decryption test for Magma is Ok" );
+  ex1: ak_bckey_context_destroy( &key );
+  if( error != ak_error_ok ) {
+    ak_error_message( ak_error_ok, __func__ , "acpkm mode test for Magma is wrong" );
+    return ak_false;
+  }
+
+ /* 2. Выполняем тест для алгоритма Кузнечик */
+  if(( error = ak_bckey_context_create_kuznechik( &key )) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect creation of kuznechik secret key" );
+    return ak_false;
+  }
+  if(( error = ak_bckey_context_set_key( &key, skey, sizeof( skey ))) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect assigning a key value" ); goto ex2; }
+
+  if(( error = ak_bckey_context_ctr_acpkm( &key, in1, out, sizeof( in1 ),
+                                                       32, iv1, sizeof( iv1 ))) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect encryption of plain text" ); goto ex2; }
+
+  if( memcmp( out, out1, sizeof( in1 )) != 0 ) {
+    ak_error_message( error = ak_error_not_equal_data, __func__,
+            "incorrect data comparizon after acpkm encryption with Kuznechik cipher" ); goto ex2; }
+ /* расшифровываем */
+  if(( error = ak_bckey_context_ctr_acpkm( &key, out1, out, sizeof( out1 ),
+                                                        32, iv1, sizeof( iv1 ))) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect encryption of plain text" ); goto ex2; }
+
+  if( memcmp( out, in1, sizeof( in1 )) != 0 ) {
+    ak_error_message( error = ak_error_not_equal_data, __func__,
+            "incorrect data comparizon after acpkm decryption with Kuznechik cipher" ); goto ex2; }
+
+  if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
+                                                "encryption/decryption test for Kuznechik is Ok" );
+  ex2: ak_bckey_context_destroy( &key );
+  if( error != ak_error_ok ) {
+    ak_error_message( ak_error_ok, __func__ , "acpkm mode test for Kuznechik is wrong" );
+    return ak_false;
+  }
+
+ return ak_true;
+}
+
+/* ----------------------------------------------------------------------------------------------- */
+/*                                                                                      ak_acpkm.c */
+/* ----------------------------------------------------------------------------------------------- */
diff --git a/source/ak_asn1.c b/source/ak_asn1.c
index c284189a..46fcc096 100644
--- a/source/ak_asn1.c
+++ b/source/ak_asn1.c
@@ -2300,8 +2300,8 @@ Validity ::= SEQUENCE {
    Время, которое помещается в структуру `Validity`, приводится из локального времени во
    время по Гринвичу. При чтении структуры должно производиться обратное преобразование.
 
-   \todo Согласно RFC 5280 даты до 2050 года сохраняются как UTCTime,
-   даты, начиная с 1 января 2050 года сохраняются как GeneralTime.
+   TODO: Согласно RFC 5280 даты до 2050 года должны сохранятся как UTCTime (это сделано)
+   даты, начиная с 1 января 2050 года должны сохранятся как GeneralTime. (это надо сделать)
 
    \param asn1 указатель на текущий уровень ASN.1 дерева.
    \param not_before начало временного интервала; локальное время, может быть получено
@@ -2781,58 +2781,58 @@ Validity ::= SEQUENCE {
 /* ----------------------------------------------------------------------------------------------- */
  int ak_asn1_context_export_to_pemfile( ak_asn1 asn, const char *filename, crypto_content_t type )
 {
-  FILE *fp = NULL;
   ak_uint8 out[4];
+  struct file ofile;
   int error = ak_error_ok;
   ak_uint8 *buffer = NULL, *ptr = NULL;
   size_t len = 0, cnt = 0, idx, blocks, tail;
 
-  /* получаем длину */
-   if(( error = ak_asn1_context_evaluate_length( asn, &len )) != ak_error_ok )
-     return ak_error_message( error, __func__, "incorrect evaluation total asn1 context length" );
+ /* получаем длину */
+  if(( error = ak_asn1_context_evaluate_length( asn, &len )) != ak_error_ok )
+    return ak_error_message( error, __func__, "incorrect evaluation total asn1 context length" );
 
-  /* кодируем/декодируем */
-   blocks = len/3;
-   if(( tail = len - 3*blocks ) != 0 ) len = 3*(blocks+1); /* увеличиваем объем до кратного трем
-                               после декодирования переменная len снова примет истинное значение */
-   if(( ptr = buffer = malloc( len )) == NULL )
-     return ak_error_message( ak_error_out_of_memory, __func__,
+ /* кодируем/декодируем */
+  blocks = len/3;
+  if(( tail = len - 3*blocks ) != 0 ) len = 3*(blocks+1); /* увеличиваем объем до кратного трем
+                              после декодирования переменная len снова примет истинное значение */
+  if(( ptr = buffer = malloc( len )) == NULL )
+    return ak_error_message( ak_error_out_of_memory, __func__,
                                                   "incorrect memory allocation for der-sequence" );
-   memset( buffer, 0, len );
-   if(( error = ak_asn1_context_encode( asn, buffer, &len )) != ak_error_ok ) {
-     ak_error_message( error, __func__, "incorrect encoding of asn1 context" );
-     goto lab2;
-   }
+  memset( buffer, 0, len );
+  if(( error = ak_asn1_context_encode( asn, buffer, &len )) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect encoding of asn1 context" );
+    goto lab2;
+  }
 
-  /* поскольку мы работаем только с текстовыми символами,
-     то для сохранения данных используется стандартная библиотека */
-   if(( fp = fopen( filename, "w" )) == NULL ) {
-     ak_error_message_fmt( error, __func__, "incorrect creation of %s", filename );
-     goto lab2;
-   }
+ /* сохраняем закодированый буффер */
+  if(( error = ak_file_create_to_write( &ofile, filename )) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect creation a file for secret key" );
+    goto lab2;
+  }
 
-   fprintf( fp, "-----BEGIN %s-----\n", crypto_content_titles[type] );
-   for( idx = 0; idx < blocks; idx++ ) {
+  ak_file_printf( &ofile, "-----BEGIN %s-----\n", crypto_content_titles[type] );
+
+  for( idx = 0; idx < blocks; idx++ ) {
      ak_base64_encodeblock( ptr, out, 3);
-     fprintf( fp, "%c%c%c%c", out[0], out[1], out[2], out[3] );
+     ak_file_printf( &ofile, "%c%c%c%c", out[0], out[1], out[2], out[3] );
      ptr += 3;
      if( ++cnt == 16 ) { /* 16х4 = 64 символа в одной строке */
-       fprintf( fp, "\n" );
+       ak_file_printf( &ofile, "\n" );
        cnt = 0;
      }
-   }
-   if( tail ) {
-     ak_base64_encodeblock( ptr, out, tail );
-     fprintf( fp, "%c%c%c%c", out[0], out[1], out[2], out[3] );
-     ++cnt;
-     fprintf( fp, "\n");
-   } else
-      if(( cnt != 16 ) && ( cnt != 0 )) fprintf( fp, "\n");
+  }
+  if( tail ) {
+    ak_base64_encodeblock( ptr, out, tail );
+    ak_file_printf( &ofile, "%c%c%c%c", out[0], out[1], out[2], out[3] );
+    ++cnt;
+    ak_file_printf( &ofile, "\n" );
+  } else
+     if(( cnt != 16 ) && ( cnt != 0 )) ak_file_printf( &ofile, "\n" );
 
-   fprintf( fp, "-----END %s-----\n", crypto_content_titles[type] );
-   fclose( fp );
+  ak_file_printf( &ofile, "-----END %s-----\n", crypto_content_titles[type] );
+  ak_file_close( &ofile );
 
-   lab2: free( buffer );
+  lab2: free( buffer );
  return error;
 }
 
diff --git a/source/ak_bckey.c b/source/ak_bckey.c
index cbc8aebb..e0303e7c 100644
--- a/source/ak_bckey.c
+++ b/source/ak_bckey.c
@@ -328,6 +328,50 @@
  return error;
 }
 
+/* ----------------------------------------------------------------------------------------------- */
+/*! @param bkey Контекст создаваемого ключа.
+    @param rkey Контекст ключа, значение которого дублируется.
+
+    @return В случае успеха возвращается значение \ref ak_error_ok. В противном случае
+    возвращается код ошибки.                                                                       */
+/* ----------------------------------------------------------------------------------------------- */
+ int ak_bckey_context_create_and_set_bckey( ak_bckey bkey, ak_bckey rkey )
+{
+  ak_oid oid = NULL;
+  int error = ak_error_ok;
+  if( bkey == NULL ) return ak_error_message( ak_error_null_pointer, __func__,
+                                              "using null pointer to left block cipher context" );
+  if( rkey == NULL ) return ak_error_message( ak_error_null_pointer, __func__,
+                                             "using null pointer to right block cipher context" );
+  if(( oid = rkey->key.oid ) == NULL )
+    return ak_error_message( ak_error_wrong_oid, __func__,
+                             "using null pointer to internal oid in right block cipher context" );
+  if( oid->func.create == NULL )
+    return ak_error_message( ak_error_undefined_function, __func__,
+                          "using null pointer to create function in right block cipher context" );
+ /* создаем объект */
+  if(( error = ((ak_function_bckey_create *)oid->func.create)( bkey )) != ak_error_ok )
+    return ak_error_message( error, __func__, "incorrect creation of left block cipher context" );
+
+ /* присваиваем ключ */
+  if(( error = rkey->key.unmask( &rkey->key )) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect unmasking block cipher context" );
+    goto  labex;
+  }
+  if(( error = ak_bckey_context_set_key( bkey,
+                                         rkey->key.key, rkey->key.key_size )) != ak_error_ok ) {
+    ak_error_message( error, __func__, "incorrect assigning a new key value" );
+  }
+  rkey->key.set_mask( &rkey->key );
+
+ return error;
+
+  labex:
+   ak_bckey_context_destroy( bkey );
+ return error;
+}
+
+
 /* ----------------------------------------------------------------------------------------------- */
 /*                             теперь реализация режимов шифрования                                */
 /* ----------------------------------------------------------------------------------------------- */
@@ -867,7 +911,7 @@
  }
 
 /* ----------------------------------------------------------------------------------------------- */
- int ak_bckey_context_cfb( ak_bckey bkey, ak_pointer in, ak_pointer out, size_t size,
+ int ak_bckey_context_encrypt_cfb( ak_bckey bkey, ak_pointer in, ak_pointer out, size_t size,
                                                                       ak_pointer iv, size_t iv_size )
  {
    ak_int64 blocks = (ak_int64)( size/bkey->bsize ),
@@ -960,6 +1004,100 @@
    return error;
 }
 
+/* ----------------------------------------------------------------------------------------------- */
+ int ak_bckey_context_decrypt_cfb( ak_bckey bkey, ak_pointer in, ak_pointer out, size_t size,
+                                                                      ak_pointer iv, size_t iv_size )
+ {
+   ak_int64 blocks = (ak_int64)( size/bkey->bsize ),
+              tail = (ak_int64)( size%bkey->bsize );
+   ak_uint8 *vecptr = NULL;
+   ak_uint64 yaout[2], *inptr = (ak_uint64 *)in, *outptr = (ak_uint64 *)out;
+   int error = ak_error_ok, oc = (int) ak_libakrypt_get_option( "openssl_compability" );
+   unsigned long i = 0, z = iv_size / bkey->bsize; // во сколько раз синхрпосылка длиннее блока
+
+   if(( oc < 0 ) || ( oc > 1 )) return ak_error_message( ak_error_wrong_option, __func__,
+                                                 "wrong value for \"openssl_compability\" option" );
+  /* проверяем целостность ключа */
+   if( bkey->key.check_icode( &bkey->key ) != ak_true )
+     return ak_error_message( ak_error_wrong_key_icode, __func__,
+                                                    "incorrect integrity code of secret key value" );
+  /* уменьшаем значение ресурса ключа */
+   if( bkey->key.resource.value.counter < ( blocks + ( tail > 0 )))
+     return ak_error_message( ak_error_low_key_resource,
+                                                     __func__ , "low resource of block cipher key" );
+    else bkey->key.resource.value.counter -= ( blocks + ( tail > 0 ));
+
+  /* выбираем, как вычислять синхропосылку проверяем флаг
+     флаг поднимается при вызове функции с заданным значением синхропосылки и
+     всегда опускается при обработке данных, не кратных длине блока */
+   if(( iv == NULL ) || ( iv_size == 0 )) { /* запрос на использование внутреннего значения */
+
+     if( bkey->key.flags&ak_key_flag_not_ctr )
+       return ak_error_message( ak_error_wrong_block_cipher_function, __func__ ,
+                                            "function call with undefined value of initial vector" );
+   } else {
+
+     /* проверяем длину синхропосылки (если меньше длины блока, то плохо)
+         если больше, то нормально - лишнее просто не используется */
+      if( (iv_size % bkey->bsize) || (iv_size > 64) )
+        return ak_error_message( ak_error_wrong_iv_length, __func__,
+                                                               "incorrect length of initial value" );
+     /* помещаем во внутренний буффер значение синхропосылки */
+      memcpy(bkey->ivector, iv, iv_size);
+
+     /* поднимаем значение флага: синхропосылка установлена */
+      bkey->key.flags = ( bkey->key.flags&( ~ak_key_flag_not_ctr ))^ak_key_flag_not_ctr;
+     }
+
+  /* обработка основного массива данных (кратного длине блока) */
+   switch( bkey->bsize ) {
+     case  8: /* шифр с длиной блока 64 бита */
+       while( blocks > 0 ) {
+           vecptr = (bkey->ivector + i*bkey->bsize);
+           bkey->encrypt( &bkey->key, vecptr, yaout );
+           *outptr = *inptr ^ yaout[0];
+           ((ak_uint64 *)vecptr)[0] = *inptr;
+           outptr++; inptr++;
+           if (++i == z) i = 0;
+           --blocks;
+       }
+     break;
+
+     case 16: /* шифр с длиной блока 128 бит */
+       while( blocks > 0 ) {
+           vecptr = (bkey->ivector + i*bkey->bsize );
+           bkey->encrypt( &bkey->key, vecptr, yaout );
+           *outptr = *inptr ^ yaout[0];
+           ((ak_uint64 *)vecptr)[0] = *inptr; ++outptr; ++inptr;
+           *outptr = *inptr ^ yaout[1];
+           ((ak_uint64 *)vecptr)[1] = *inptr; ++outptr; ++inptr;
+           if (++i == z) i = 0;
+           --blocks;
+       }
+     break;
+
+     default: return ak_error_message( ak_error_wrong_block_cipher,
+                                           __func__ , "incorrect block size of block cipher key" );
+   }
+
+  /* обрабатываем хвост сообщения */
+   if( tail ) {
+     vecptr = (bkey->ivector + bkey->bsize * (i % (int)(iv_size / bkey->bsize)));
+     bkey->encrypt( &bkey->key, vecptr, yaout );
+     for( i = 0; i < (unsigned long)tail; i++ )
+        ( (ak_uint8*)outptr)[i] = ( (ak_uint8*)inptr )[i]^( (ak_uint8 *)yaout)[i];
+
+     /* запрещаем дальнейшее использование функции на данном значении синхропосылки,
+                                               поскольку обрабатываемые данные не кратны длине блока. */
+     memset( bkey->ivector, 0, sizeof( bkey->ivector ));
+     bkey->key.flags = bkey->key.flags&( ~ak_key_flag_not_ctr );
+     /* перемаскируем ключ */
+     if(( error = bkey->key.set_mask( &bkey->key )) != ak_error_ok )
+        ak_error_message( error, __func__ , "wrong remasking of secret key" );
+   }
+   return error;
+}
+
 /* ----------------------------------------------------------------------------------------------- */
 /*! Функция вычисляет имитовставку от заданной области памяти фиксированного размера.
    Используется алгоритм, который также называют OMAC1
diff --git a/source/ak_bckey.h b/source/ak_bckey.h
index b6e20bf6..931f96c9 100644
--- a/source/ak_bckey.h
+++ b/source/ak_bckey.h
@@ -69,7 +69,7 @@
  int ak_bckey_context_set_key_random( ak_bckey , ak_random );
 /*! \brief Присвоение контексту ключа алгоритма блочного шифрования значения, выработанного из пароля. */
  int ak_bckey_context_set_key_from_password( ak_bckey , const ak_pointer , const size_t ,
-                                                                const ak_pointer , const size_t );
+                                                                 const ak_pointer , const size_t );
 /* ----------------------------------------------------------------------------------------------- */
 /*! \brief Инициализация ключа алгоритма блочного шифрования значением другого ключа */
  int ak_bckey_context_create_and_set_bckey( ak_bckey , ak_bckey );
@@ -84,10 +84,12 @@
  int ak_bckey_context_decrypt_ecb( ak_bckey , ak_pointer , ak_pointer , size_t );
  /*! \brief Зашифрование данных в режиме простой замены с зацеплением из ГОСТ Р 34.13-2015
     (cipher block chaining, cbc). */
- int ak_bckey_context_encrypt_cbc( ak_bckey , ak_pointer , ak_pointer , size_t , ak_pointer , size_t );
+ int ak_bckey_context_encrypt_cbc( ak_bckey , ak_pointer , ak_pointer , size_t ,
+                                                                             ak_pointer , size_t );
  /*! \brief Расшифрование данных в режиме простой замены с зацеплением из ГОСТ Р 34.13-2015
     (cipher block chaining, cbc). */
- int ak_bckey_context_decrypt_cbc( ak_bckey , ak_pointer , ak_pointer , size_t , ak_pointer , size_t );
+ int ak_bckey_context_decrypt_cbc( ak_bckey , ak_pointer , ak_pointer , size_t ,
+                                                                             ak_pointer , size_t );
 /*! \brief Шифрование данных в режиме гаммирования из ГОСТ Р 34.13-2015
    (counter mode, ctr). */
  int ak_bckey_context_ctr( ak_bckey , ak_pointer , ak_pointer , size_t , ak_pointer , size_t );
@@ -96,15 +98,18 @@
  int ak_bckey_context_ofb( ak_bckey , ak_pointer , ak_pointer , size_t , ak_pointer , size_t );
 /*! \brief Шифрование данных в режиме гаммирования с обратной связью по шифртексту
    (cipher feedback, cfb). */
- int ak_bckey_context_cfb( ak_bckey , ak_pointer , ak_pointer , size_t , ak_pointer , size_t );
-
+ int ak_bckey_context_encrypt_cfb( ak_bckey , ak_pointer , ak_pointer , size_t ,
+                                                                             ak_pointer , size_t );
+/*! \brief Расшифрование данных в режиме гаммирования с обратной связью по шифртексту
+   (cipher feedback, cfb). */
+ int ak_bckey_context_decrypt_cfb( ak_bckey , ak_pointer , ak_pointer , size_t ,
+                                                                             ak_pointer , size_t );
 /*! \brief Шифрование данных в режиме CTR-ACPKM из Р 1323565.1.017—2018. */
  int ak_bckey_context_ctr_acpkm( ak_bckey , ak_pointer , ak_pointer , size_t , size_t ,
-                                                                           ak_pointer , size_t );
+                                                                             ak_pointer , size_t );
 /*! \brief Вычисление имитовставки согласно ГОСТ Р 34.13-2015. */
  int ak_bckey_context_cmac( ak_bckey , ak_pointer , const size_t , ak_pointer , const size_t );
 
-
 /* ----------------------------------------------------------------------------------------------- */
 /*! \brief Выработка матрицы, соответствующей 16 тактам работы линейного региста сдвига. */
  void ak_bckey_context_kuznechik_generate_matrix( const linear_register , linear_matrix );
diff --git a/source/ak_kuznechik.c b/source/ak_kuznechik.c
index 994dd18f..d0d34e4d 100644
--- a/source/ak_kuznechik.c
+++ b/source/ak_kuznechik.c
@@ -770,7 +770,6 @@
   int error = ak_error_ok, audit = ak_log_get_level(),
       oc = (int) ak_libakrypt_get_option( "openssl_compability" );
 
- /* тестовый ключ из ГОСТ Р 34.12-2015, приложение А.1 */
  /* тестовый ключ из ГОСТ Р 34.13-2015, приложение А.1 */
   ak_uint8 key[32] = {
     0xef,0xcd,0xab,0x89,0x67,0x45,0x23,0x01,0x10,0x32,0x54,0x76,0x98,0xba,0xdc,0xfe,
@@ -858,6 +857,47 @@
     0x16, 0x76, 0x88, 0x06, 0x5a, 0x89, 0x5c, 0x63, 0x1a, 0x2d, 0x9a, 0x15, 0x60, 0xb6, 0x39, 0x70
   };
 
+ /* инициализационный вектор для режима гаммирования с обратной связью по выходу (ofb) */
+  ak_uint8 ivofb[32] = {
+    0x12, 0x01, 0xf0, 0xe5, 0xd4, 0xc3, 0xb2, 0xa1, 0xf0, 0xce, 0xab, 0x90, 0x78, 0x56, 0x34, 0x12,
+    0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x90, 0x89, 0x78, 0x67, 0x56, 0x45, 0x34, 0x23
+  };
+
+ /* значение синхропосылки для командной строки:
+    1234567890abcef0a1b2c3d4e5f0011223344556677889901213141516171819 */
+  ak_uint8 openssl_ivofb[32] = {
+    0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xce, 0xf0, 0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf0, 0x01, 0x12,
+    0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x90, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19
+  };
+
+ /* зашифрованный блок из ГОСТ Р 34.13-2015, прил. А.1.3*/
+  ak_uint8 outofb[64] = {
+    0x95, 0xbd, 0x7a, 0x89, 0x5e, 0x79, 0x1f, 0xff, 0x24, 0x2b, 0x84, 0xb1, 0x59, 0x0a, 0x80, 0x81,
+    0xbf, 0x26, 0x93, 0x9d, 0x36, 0x21, 0xb5, 0x8f, 0xb4, 0xfa, 0x8c, 0x04, 0xa7, 0x47, 0x5b, 0xed,
+    0x13, 0x8a, 0x28, 0x10, 0xfc, 0xe7, 0x0f, 0xc8, 0xb1, 0xb8, 0xa0, 0x3c, 0xac, 0x57, 0xa2, 0x66,
+    0x50, 0x31, 0x90, 0xf6, 0x43, 0x22, 0x29, 0xa0, 0x60, 0x86, 0x13, 0x66, 0xc0, 0xbb, 0x3e, 0x20
+  };
+  ak_uint8 openssl_outofb[64] = {
+    0x81, 0x80, 0x0a, 0x59, 0xb1, 0x84, 0x2b, 0x24, 0xff, 0x1f, 0x79, 0x5e, 0x89, 0x7a, 0xbd, 0x95,
+    0xed, 0x5b, 0x47, 0xa7, 0x04, 0x8c, 0xfa, 0xb4, 0x8f, 0xb5, 0x21, 0x36, 0x9d, 0x93, 0x26, 0xbf,
+    0x66, 0xa2, 0x57, 0xac, 0x3c, 0xa0, 0xb8, 0xb1, 0xc8, 0x0f, 0xe7, 0xfc, 0x10, 0x28, 0x8a, 0x13,
+    0x20, 0x3e, 0xbb, 0xc0, 0x66, 0x13, 0x86, 0x60, 0xa0, 0x29, 0x22, 0x43, 0xf6, 0x90, 0x31, 0x50
+  };
+
+ /* зашифрованный блок из ГОСТ Р 34.13-2015, прил. А.1.5 */
+  ak_uint8 outcfb[64] = {
+    0x95, 0xbd, 0x7a, 0x89, 0x5e, 0x79, 0x1f, 0xff, 0x24, 0x2b, 0x84, 0xb1, 0x59, 0x0a, 0x80, 0x81,
+    0xbf, 0x26, 0x93, 0x9d, 0x36, 0x21, 0xb5, 0x8f, 0xb4, 0xfa, 0x8c, 0x04, 0xa7, 0x47, 0x5b, 0xed,
+    0xb5, 0x38, 0xa2, 0x97, 0x4e, 0x26, 0x2d, 0x84, 0x38, 0x8d, 0xc6, 0x5c, 0xeb, 0xa8, 0xf2, 0x79,
+    0xd1, 0xf4, 0xfb, 0x44, 0xdd, 0xd9, 0x5b, 0xc7, 0xe6, 0x2d, 0x92, 0x4e, 0xcd, 0xbe, 0xfe, 0x4f
+  };
+  ak_uint8 openssl_outcfb[64] = {
+    0x81, 0x80, 0x0a, 0x59, 0xb1, 0x84, 0x2b, 0x24, 0xff, 0x1f, 0x79, 0x5e, 0x89, 0x7a, 0xbd, 0x95,
+    0xed, 0x5b, 0x47, 0xa7, 0x04, 0x8c, 0xfa, 0xb4, 0x8f, 0xb5, 0x21, 0x36, 0x9d, 0x93, 0x26, 0xbf,
+    0x79, 0xf2, 0xa8, 0xeb, 0x5c, 0xc6, 0x8d, 0x38, 0x84, 0x2d, 0x26, 0x4e, 0x97, 0xa2, 0x38, 0xb5,
+    0x4f, 0xfe, 0xbe, 0xcd, 0x4e, 0x92, 0x2d, 0xe6, 0xc7, 0x5b, 0xd9, 0xdd, 0x44, 0xfb, 0xf4, 0xd1
+  };
+
  /* значение имитовставки согласно ГОСТ Р 34.13-2015 (раздел А.1.6) */
   ak_uint8 imito[8] = {
     /* 0x67, 0x9C, 0x74, 0x37, 0x5B, 0xB3, 0xDE, 0x4D - первая часть выработанного блока */
@@ -884,7 +924,9 @@
     return ak_false;
   }
 
+ /* --------------------------------------------------------------------------- */
  /* 1. Создаем контекст ключа алгоритма Кузнечик и устанавливаем значение ключа */
+ /* --------------------------------------------------------------------------- */
   if(( error = ak_bckey_context_create_kuznechik( &bkey )) != ak_error_ok ) {
     ak_error_message( error, __func__, "incorrect initialization of kuznechik secret key context");
     return ak_false;
@@ -897,7 +939,9 @@
     goto exit;
   }
 
+ /* ------------------------------------------------------------------------------------------- */
  /* 2. Проверяем независимую обработку блоков - режим простой замены согласно ГОСТ Р 34.12-2015 */
+ /* ------------------------------------------------------------------------------------------- */
   if(( error = ak_bckey_context_encrypt_ecb( &bkey, oc ? oc_in : in,
                                                            myout, sizeof( in ))) != ak_error_ok ) {
     ak_error_message( error, __func__ , "wrong ecb mode encryption" );
@@ -926,7 +970,9 @@
   if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
                 "the ecb mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
 
- /* 3. Проверяем режим гаммирования согласно ГОСТ Р 34.12-2015 */
+ /* --------------------------------------------------------------------------- */
+ /* 3. Проверяем режим гаммирования согласно ГОСТ Р 34.12-2015                  */
+ /* --------------------------------------------------------------------------- */
   if(( error = ak_bckey_context_ctr( &bkey, oc ? oc_in : in,
                    myout, sizeof( in ), oc ? oc_ivctr : ivctr, sizeof( ivctr ))) != ak_error_ok ) {
     ak_error_message( error, __func__ , "wrong counter mode encryption" );
@@ -942,7 +988,7 @@
 
   if(( error = ak_bckey_context_ctr( &bkey, myout,
                myout, sizeof( outecb ), oc ? oc_ivctr : ivctr, sizeof( ivctr ))) != ak_error_ok ) {
-    ak_error_message( error, __func__ , "wrong ecb mode decryption" );
+    ak_error_message( error, __func__ , "wrong counter mode decryption" );
     result = ak_false;
     goto exit;
   }
@@ -955,7 +1001,9 @@
   if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
                 "the counter mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
 
- /* 4. Проверяем режим простой замены c зацеплением согласно ГОСТ Р 34.12-2015 */
+ /* --------------------------------------------------------------------------- */
+ /* 4. Проверяем режим простой замены c зацеплением согласно ГОСТ Р 34.12-2015  */
+ /* --------------------------------------------------------------------------- */
   if(( error = ak_bckey_context_encrypt_cbc( &bkey, oc ? oc_in : in, myout, sizeof( in ),
                                    oc ? openssl_ivcbc : ivcbc, sizeof( ivcbc ))) != ak_error_ok ) {
     ak_error_message( error, __func__ , "wrong cbc mode encryption" );
@@ -983,8 +1031,72 @@
   if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
                          "the cbc mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
 
+ /* -------------------------------------------------------------------------------------- */
+ /* 5. Проверяем режим гаммирования c обратной связью по выходу согласно ГОСТ Р 34.12-2015 */
+ /* -------------------------------------------------------------------------------------- */
+  if(( error = ak_bckey_context_ofb( &bkey, oc ? oc_in : in,
+              myout, sizeof( in ), oc ? openssl_ivofb : ivofb, sizeof( ivofb ))) != ak_error_ok ) {
+    ak_error_message( error, __func__ , "wrong ofb mode encryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? openssl_outofb : outofb, sizeof( outofb ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the ofb mode encryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+
+  if(( error = ak_bckey_context_ofb( &bkey, oc ? openssl_outofb : outofb,
+          myout, sizeof( outofb ), oc ? openssl_ivofb : ivofb, sizeof( ivofb ))) != ak_error_ok ) {
+    ak_error_message( error, __func__ , "wrong ofb mode decryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? oc_in : in, sizeof( in ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the ofb mode decryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+  if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
+                "the ofb mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
+
+ /* -------------------------------------------------------------------------------------- */
+ /* 6. Проверяем режим гаммирования c обратной связью по шифртексту */
+ /* -------------------------------------------------------------------------------------- */
+  if(( error = ak_bckey_context_encrypt_cfb( &bkey, oc ? oc_in : in,
+              myout, sizeof( in ), oc ? openssl_ivofb : ivofb, sizeof( ivofb ))) != ak_error_ok ) {
+               /* используемая синхропосылка совпадает с вектором из режима ofb */
+    ak_error_message( error, __func__ , "wrong cfb mode encryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? openssl_outcfb : outcfb, sizeof( outcfb ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the cfb mode encryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+
+  if(( error = ak_bckey_context_decrypt_cfb( &bkey, oc ? openssl_outcfb : outcfb,
+          myout, sizeof( outcfb ), oc ? openssl_ivofb : ivofb, sizeof( ivofb ))) != ak_error_ok ) {
+    ak_error_message( error, __func__ , "wrong cfb mode decryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? oc_in : in, sizeof( in ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the cfb mode decryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+  if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
+                "the cfb mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
 
- /* 10. Тестируем режим выработки имитовставки (плоская реализация). */
+ /* --------------------------------------------------------------------------- */
+ /* 10. Тестируем режим выработки имитовставки (плоская реализация).            */
+ /* --------------------------------------------------------------------------- */
   if(( error = ak_bckey_context_cmac( &bkey, oc ? oc_in : in,
                                                      sizeof( in ), myout, 8 )) != ak_error_ok ) {
     ak_error_message( error, __func__ , "wrong cmac calculation" );
diff --git a/source/ak_libakrypt.c b/source/ak_libakrypt.c
index 45ad3318..e6d42b67 100644
--- a/source/ak_libakrypt.c
+++ b/source/ak_libakrypt.c
@@ -180,11 +180,11 @@
 //                                               "incorrect testing of mgm mode for block ciphers" );
 //    return ak_false;
 //  }
-//  if( ak_bckey_test_acpkm()  != ak_true ) {
-//    ak_error_message( ak_error_get_value(), __func__ ,
-//                                  "incorrect testing of acpkm encryption mode for block ciphers" );
-//    return ak_false;
-//  }
+  if( ak_bckey_test_acpkm()  != ak_true ) {
+    ak_error_message( ak_error_get_value(), __func__ ,
+                                  "incorrect testing of acpkm encryption mode for block ciphers" );
+    return ak_false;
+  }
 
   if( audit >= ak_log_maximum )
    ak_error_message( ak_error_ok, __func__ , "testing block ciphers ended successfully" );
diff --git a/source/ak_magma.c b/source/ak_magma.c
index 314f7b5c..639746fe 100644
--- a/source/ak_magma.c
+++ b/source/ak_magma.c
@@ -737,7 +737,20 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
     0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef, 0x12
   };
 
- /* зашифрованный блок из ГОСТ Р 34.12-2015 */
+ /* инициализационный вектор для режима гаммирования с обратной связью по выходу
+      Рё РѕРЅ Р¶Рµ
+    инициализационный вектор для режима гаммирования с обратной связью по шифртексту */
+  ak_uint8 magma_ivofb[16] = {
+    0xef, 0xcd, 0xab, 0x90, 0x78, 0x56, 0x34, 0x12,
+    0xf1, 0xde, 0xbc, 0x0a, 0x89, 0x67, 0x45, 0x23,
+  };
+
+  ak_uint8 openssl_magma_ivofb[16] = {
+    0x12, 0x34, 0x56, 0x78, 0x90, 0xab, 0xcd, 0xef,
+    0x23, 0x45, 0x67, 0x89, 0x0a, 0xbc, 0xde, 0xf1,
+  };
+
+ /* зашифрованный блок из ГОСТ Р 34.12-2015 для режима простой замены */
   ak_uint8 magma_out_cbc[32] = {
     0x19, 0x39, 0x68, 0xea, 0x5e, 0xb0, 0xd1, 0x96,
     0xb9, 0x37, 0xb9, 0xab, 0x29, 0x61, 0xf7, 0xaf,
@@ -751,6 +764,35 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
     0x20, 0xb7, 0x8b, 0x1a, 0x7c, 0xd7, 0xe6, 0x67
   };
 
+ /* зашифрованный блок из ГОСТ Р 34.12-2015 для режима гаммирования с обратной связью по выходу */
+  ak_uint8 magma_outofb[32] = {
+    0x83, 0x3c, 0x90, 0x66, 0xe2, 0xe0, 0x37, 0xdb,
+    0x9c, 0x08, 0x9a, 0x1f, 0x4c, 0x64, 0x46, 0x0d,
+    0x7e, 0x32, 0x0e, 0x43, 0x62, 0x30, 0xf8, 0xa0,
+    0x05, 0xdb, 0x4f, 0xbd, 0xb8, 0xef, 0x24, 0xc8
+  };
+
+  ak_uint8 openssl_magma_outofb[32] = {
+    0xdb, 0x37, 0xe0, 0xe2, 0x66, 0x90, 0x3c, 0x83,
+    0x0d, 0x46, 0x64, 0x4c, 0x1f, 0x9a, 0x08, 0x9c,
+    0xa0, 0xf8, 0x30, 0x62, 0x43, 0x0e, 0x32, 0x7e,
+    0xc8, 0x24, 0xef, 0xb8, 0xbd, 0x4f, 0xdb, 0x05
+  };
+
+ /* зашифрованный блок из ГОСТ Р 34.12-2015 для режима гаммирования с обратной связью по шифртексту */
+  ak_uint8 magma_outcfb[32] = {
+    0x83, 0x3c, 0x90, 0x66, 0xe2, 0xe0, 0x37, 0xdb,
+    0x9c, 0x08, 0x9a, 0x1f, 0x4c, 0x64, 0x46, 0x0d,
+    0x8b, 0xd3, 0x15, 0x53, 0x03, 0xd2, 0xbd, 0x24,
+    0x05, 0x55, 0x07, 0x21, 0x14, 0x32, 0xc0, 0xbc
+  };
+  ak_uint8 openssl_magma_outcfb[32] = {
+    0xdb, 0x37, 0xe0, 0xe2, 0x66, 0x90, 0x3c, 0x83,
+    0x0d, 0x46, 0x64, 0x4c, 0x1f, 0x9a, 0x08, 0x9c,
+    0x24, 0xbd, 0xd2, 0x03, 0x53, 0x15, 0xd3, 0x8b,
+    0xbc, 0xc0, 0x32, 0x14, 0x21, 0x07, 0x55, 0x05
+  };
+
  /* значение имитовставки согласно ГОСТ Р 34.13-2015 (раздел А.2.6) */
   ak_uint8 imito[4] = {
     /* 0xbb, 0xc5, 0x20, 0x30 - первая часть выработанного блока */
@@ -792,7 +834,9 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
      }
   }
 
+ /* ------------------------------------------------------------------------ */
  /* 1. Создаем контекст ключа алгоритма Магма и устанавливаем значение ключа */
+ /* ------------------------------------------------------------------------ */
   if(( error = ak_bckey_context_create_magma( &mkey )) != ak_error_ok ) {
     ak_error_message( error, __func__, "incorrect initialization of magma secret key context");
     return ak_false;
@@ -805,7 +849,9 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
     goto exit;
   }
 
+ /* ------------------------------------------------------------------------------------------- */
  /* 2. Проверяем независимую обработку блоков - режим простой замены согласно ГОСТ Р 34.12-2015 */
+ /* ------------------------------------------------------------------------------------------- */
   if(( error = ak_bckey_context_encrypt_ecb( &mkey, oc ? openssl_magma_in : magma_in,
                                                      myout, sizeof( magma_in ))) != ak_error_ok ) {
     ak_error_message( error, __func__ , "wrong ecb mode encryption" );
@@ -835,7 +881,9 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
   if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
                 "the ecb mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
 
- /* 3. Проверяем режим гаммирования согласно ГОСТ Р 34.12-2015 */
+ /* ----------------------------------------------------------------- */
+ /* 3. Проверяем режим гаммирования согласно ГОСТ Р 34.12-2015        */
+ /* ----------------------------------------------------------------- */
   if(( error = ak_bckey_context_ctr( &mkey, oc ? openssl_magma_in : magma_in,
                    myout, sizeof( magma_in ), oc ? openssl_magma_ivctr : magma_ivctr,
                                                          sizeof( magma_ivctr ))) != ak_error_ok ) {
@@ -854,7 +902,7 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
   if(( error = ak_bckey_context_ctr( &mkey, oc ? openssl_magma_out_ctr : magma_out_ctr,
                         myout, sizeof( magma_out_ecb ), oc ? openssl_magma_ivctr : magma_ivctr,
                                                          sizeof( magma_ivctr ))) != ak_error_ok ) {
-    ak_error_message( error, __func__ , "wrong ecb mode decryption" );
+    ak_error_message( error, __func__ , "wrong counter mode decryption" );
     result = ak_false;
     goto exit;
   }
@@ -868,7 +916,9 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
                 "the counter mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
 
 
- /* 4. Проверяем режим простой замены с зацеплением (cbc) */
+ /* ----------------------------------------------------------------- */
+ /* 4. Проверяем режим простой замены с зацеплением (cbc)             */
+ /* ----------------------------------------------------------------- */
   if(( error = ak_bckey_context_encrypt_cbc( &mkey, oc ? openssl_magma_in : magma_in,
                  myout, sizeof( magma_in ), oc ? openssl_magma_ivcbc : magma_ivcbc,
                                                          sizeof( magma_ivcbc ))) != ak_error_ok ) {
@@ -886,22 +936,91 @@ int ak_bckey_context_create_magma( ak_bckey bkey )
   if(( error = ak_bckey_context_decrypt_cbc( &mkey, oc ? openssl_magma_out_cbc : magma_out_cbc,
                  myout, sizeof( magma_in ), oc ? openssl_magma_ivcbc : magma_ivcbc,
                                                          sizeof( magma_ivcbc ))) != ak_error_ok ) {
-    ak_error_message( error, __func__ , "wrong cbc mode encryption" );
+    ak_error_message( error, __func__ , "wrong cbc mode decryption" );
     result = ak_false;
     goto exit;
   }
   if( !ak_ptr_is_equal_with_log( myout, oc ? openssl_magma_in :
                                                               magma_in, sizeof( magma_out_cbc ))) {
     ak_error_message( ak_error_not_equal_data, __func__ ,
-                        "the cbc mode encryption test from GOST R 34.13-2015 is wrong");
+                        "the cbc mode decryption test from GOST R 34.13-2015 is wrong");
     result = ak_false;
     goto exit;
   }
   if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
                 "the cbc mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
 
+ /* ----------------------------------------------------------------- */
+ /* 5. Проверяем режим гаммирования с обратной связью по выходу (ofb) */
+ /* ----------------------------------------------------------------- */
+  if(( error = ak_bckey_context_ofb( &mkey, oc ? openssl_magma_in : magma_in,
+                 myout, sizeof( magma_in ), oc ? openssl_magma_ivofb : magma_ivofb,
+                                                         sizeof( magma_ivofb ))) != ak_error_ok ) {
+    ak_error_message( error, __func__ , "wrong ofb mode encryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? openssl_magma_outofb :
+                                                           magma_outofb, sizeof( magma_outofb ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the ofb mode encryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+  if(( error = ak_bckey_context_ofb( &mkey, oc ? openssl_magma_outofb : magma_outofb,
+                 myout, sizeof( magma_in ), oc ? openssl_magma_ivofb : magma_ivofb,
+                                                         sizeof( magma_ivofb ))) != ak_error_ok ) {
+    ak_error_message( error, __func__ , "wrong ofb mode decryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? openssl_magma_in : magma_in, sizeof( magma_outofb ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the ofb mode decryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+  if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
+                "the ofb mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
+
+
+ /* ----------------------------------------------------------------- */
+ /* 6. Проверяем режим гаммирования с обратной связью по выходу (cfb) */
+ /* ----------------------------------------------------------------- */
+  if(( error = ak_bckey_context_encrypt_cfb( &mkey, oc ? openssl_magma_in : magma_in,
+                 myout, sizeof( magma_in ), oc ? openssl_magma_ivofb : magma_ivofb, /* синхропосылка одна и та же */
+                                                         sizeof( magma_ivofb ))) != ak_error_ok ) {
+    ak_error_message( error, __func__ , "wrong cfb mode encryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? openssl_magma_outcfb :
+                                                           magma_outcfb, sizeof( magma_outcfb ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the cfb mode encryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+  if(( error = ak_bckey_context_decrypt_cfb( &mkey, oc ? openssl_magma_outcfb : magma_outcfb,
+                 myout, sizeof( magma_outcfb ), oc ? openssl_magma_ivofb : magma_ivofb,
+                                                         sizeof( magma_ivofb ))) != ak_error_ok ) {
+    ak_error_message( error, __func__ , "wrong cfb mode decryption" );
+    result = ak_false;
+    goto exit;
+  }
+  if( !ak_ptr_is_equal_with_log( myout, oc ? openssl_magma_in : magma_in, sizeof( magma_in ))) {
+    ak_error_message( ak_error_not_equal_data, __func__ ,
+                        "the cfb mode decryption test from GOST R 34.13-2015 is wrong");
+    result = ak_false;
+    goto exit;
+  }
+  if( audit >= ak_log_maximum ) ak_error_message( ak_error_ok, __func__ ,
+                "the cfb mode encryption/decryption test from GOST R 34.13-2015 is Ok" );
+
 
- /* 10. Тестируем режим выработки имитовставки (плоская реализация). */
+ /* ----------------------------------------------------------------- */
+ /* 10. Тестируем режим выработки имитовставки (плоская реализация).  */
+ /* ----------------------------------------------------------------- */
   if(( error = ak_bckey_context_cmac( &mkey, oc ? openssl_magma_in : magma_in,
                                                  sizeof( magma_in ), myout, 4 )) != ak_error_ok ) {
     ak_error_message( error, __func__ , "wrong cmac calculation" );
diff --git a/source/ak_sign.c b/source/ak_sign.c
index dde91a19..a02950a1 100644
--- a/source/ak_sign.c
+++ b/source/ak_sign.c
@@ -159,7 +159,7 @@
 }
 
 /* ----------------------------------------------------------------------------------------------- */
-/*! @todo Необходимо реализовать выработку контрольной суммы для секретного ключа ЭП. */
+/* TODO: Необходимо реализовать выработку контрольной суммы для секретного ключа ЭП. */
  static int ak_signkey_context_set_icode_multiplicative( ak_skey skey )
 {
  (void) skey;
@@ -167,7 +167,7 @@
 }
 
 /* ----------------------------------------------------------------------------------------------- */
-/*! @todo Необходимо реализовать проверку контрольной суммы для секретного ключа ЭП. */
+/* TODO: Необходимо реализовать проверку контрольной суммы для секретного ключа ЭП. */
  static bool_t ak_signkey_context_check_icode_multiplicative( ak_skey skey )
 {
  (void) skey;
diff --git a/source/ak_tools.c b/source/ak_tools.c
index 8647019c..3294f042 100644
--- a/source/ak_tools.c
+++ b/source/ak_tools.c
@@ -785,6 +785,36 @@
  #endif
 }
 
+/* ----------------------------------------------------------------------------------------------- */
+ ssize_t ak_file_printf( ak_file outfile, const char *format, ... )
+{
+  va_list args;
+  ssize_t result = 0;
+  va_start( args, format );
+
+ /* формируем строку (дублируем код функции ak_snprintf) */
+ #ifdef _MSC_VER
+  #if _MSC_VER > 1310
+    _vsnprintf_s( ak_ptr_to_hexstr_static_buffer,
+                  sizeof( ak_ptr_to_hexstr_static_buffer ),
+                  sizeof( ak_ptr_to_hexstr_static_buffer ), format, args );
+  #else
+    _vsnprintf( ak_ptr_to_hexstr_static_buffer,
+                sizeof( ak_ptr_to_hexstr_static_buffer ), format, args );
+  #endif
+ #else
+  vsnprintf( ak_ptr_to_hexstr_static_buffer,
+             sizeof( ak_ptr_to_hexstr_static_buffer ), format, args );
+ #endif
+  va_end( args );
+
+ /* выводим ее в файл как последовательность байт */
+  result = ak_file_write( outfile,
+                          ak_ptr_to_hexstr_static_buffer,
+                          strlen( ak_ptr_to_hexstr_static_buffer ));
+ return result;
+}
+
 /* ----------------------------------------------------------------------------------------------- */
 /*! \hidecallgraph
     \hidecallergraph                                                                               */
diff --git a/source/ak_tools.h b/source/ak_tools.h
index 65c589b5..be57844c 100644
--- a/source/ak_tools.h
+++ b/source/ak_tools.h
@@ -46,14 +46,14 @@
  int ak_file_open_to_read( ak_file , const char * ); 
 /*! \brief Функция создает файл с правами на запись. */
  int ak_file_create_to_write( ak_file , const char * );
-/*! \brief Функция открывает заданный файл с правами запись (используется для дополнения существующих данных). */
- int ak_file_open_to_append( ak_file , const char * );
 /*! \brief Функция закрывает файл с заданным дескриптором. */
  int ak_file_close( ak_file );
 /*! \brief Функция считывает заданное количество байт из файла. */
  ssize_t ak_file_read( ak_file , ak_pointer , size_t );
 /*! \brief Функция записывает заданное количество байт в файл. */
  ssize_t ak_file_write( ak_file , ak_const_pointer , size_t );
+/*! \brief Функция записывает в файл строку символов. */
+ ssize_t ak_file_printf( ak_file , const char * , ... );
 
 /* ----------------------------------------------------------------------------------------------- */
 /*! \brief Функция устанавливает значение опции с заданным именем. */
diff --git a/tests/test-asn1-build.c b/tests/test-asn1-build.c
index 0d931834..811ea725 100644
--- a/tests/test-asn1-build.c
+++ b/tests/test-asn1-build.c
@@ -141,6 +141,8 @@
   }
    else printf(" Wrong\n");
   ak_hash_context_destroy( &ctx );
+#else
+    result = EXIT_SUCCESS;
 #endif
 
  /* уничтожаем дерево и выходим */
diff --git a/tests/test-bckey04.c b/tests/test-bckey04.c
index a1e8b316..115fa109 100644
--- a/tests/test-bckey04.c
+++ b/tests/test-bckey04.c
@@ -23,7 +23,24 @@
 
  typedef int ( efunction )( ak_bckey , ak_pointer , ak_pointer , size_t , ak_pointer , size_t );
  void test( char *, efunction *, ak_bckey );
+ int ecb_fixed( ak_bckey bkey, ak_pointer in, ak_pointer out,
+                                                  size_t size, ak_pointer iv, size_t ivsize ) {
+  (void) iv;
+  (void) ivsize;
+  return ak_bckey_context_encrypt_ecb( bkey, in, out, size );
+ }
+ int acpkm_fixed( ak_bckey bkey, ak_pointer in, ak_pointer out,
+                                                  size_t size, ak_pointer iv, size_t ivsize ) {
+   return ak_bckey_context_ctr_acpkm( bkey, in, out, size, 8192, iv, ivsize );
+                               /* выполнено равенство 8192 / 16 = 512,
+                                  где 16 длина блока, 512 = acpkm_section_kuznechik_block_count
+                                  это количество блоков для одного ключа                       */
+ }
 
+/* -------------------------------------------------------------------------------------- */
+
+
+/* -------------------------------------------------------------------------------------- */
  int main( void )
 {
   struct bckey ctx;
@@ -33,12 +50,14 @@
 
  /* статический объект существует, но он требует инициализации */
   printf("key create: %d\n", ak_bckey_context_create_kuznechik( &ctx ));
-  printf("key set value: %d\n", ak_bckey_context_set_key( &ctx, key, sizeof( key )));
+  printf("key set value: %d\n\n", ak_bckey_context_set_key( &ctx, key, sizeof( key )));
 
-  test( "CFB", ak_bckey_context_cfb, &ctx );
+  test( "ECB", ecb_fixed, &ctx );
+  test( "CFB", ak_bckey_context_encrypt_cfb, &ctx );
   test( "OFB", ak_bckey_context_ofb, &ctx );
   test( "CBC", ak_bckey_context_encrypt_cbc, &ctx );
   test( "CTR", ak_bckey_context_ctr, &ctx );
+  test( "ACPKM", acpkm_fixed, &ctx );
 
   ak_bckey_context_destroy( &ctx );
  return ak_libakrypt_destroy();
@@ -53,6 +72,7 @@
   size_t size;
   double iter = 0, avg = 0;
 
+  printf("%s\t[16MB ", STR );
   for( i = 16; i < 129; i += 8 ) {
     data = malloc( size = ( size_t ) i*1024*1024 );
     memset( data, (ak_uint8)i+1, size );
@@ -60,16 +80,18 @@
     timea = clock();
     fun( ctx, data, data, size, iv, sizeof( iv ));
     timea = clock() - timea;
-    printf(" %3uMB: %s time: %fs, per 1MB = %fs, speed = %f MBs\n", (unsigned int)i, STR,
+/*  детальный вывод
+    printf(" %3uMB: %s time: %fs, per 1MB = %fs, speed = %3f MBs\n", (unsigned int)i, STR,
                (double) timea / (double) CLOCKS_PER_SEC,
                (double) timea / ( (double) CLOCKS_PER_SEC*i ),
-               (double) CLOCKS_PER_SEC*i / (double) timea );
+               (double) CLOCKS_PER_SEC*i / (double) timea ); */
+    printf("."); fflush(stdout);
     if( i > 16 ) {
       iter += 1;
       avg += (double) CLOCKS_PER_SEC*i / (double) timea;
     }
     free( data );
   }
-  printf("average memory %s speed: %f MByte/sec\n\n", STR, avg/iter );
+  printf(" 128MB] average memory speed: %12f MByte/sec\n", avg/iter );
 
 }
diff --git a/tests/test-bckey05-cfb.c b/tests/test-bckey05-cfb.c
index 4109170a..1d6f7d0c 100644
--- a/tests/test-bckey05-cfb.c
+++ b/tests/test-bckey05-cfb.c
@@ -143,7 +143,7 @@
 
  /* зашифровываем и расшифровываем всего четыре блока данных */
   printf("Kuznechik\n");
-  ak_bckey_context_cfb( &bkey, oc ? openssl_in : in, buf, sizeof( in ),
+  ak_bckey_context_encrypt_cfb( &bkey, oc ? openssl_in : in, buf, sizeof( in ),
                                                   oc ? openssl_ivcfb : ivcfb, 32 );
   printf("encrypted:\n");
   for( i = 0; i < 4; i++ ) {
@@ -165,7 +165,7 @@
 
  /* зашифровываем и расшифровываем всего четыре блока данных */
   printf("Magma\n");
-  ak_bckey_context_cfb( &mkey, oc ? openssl_magma_in : magma_in, buf, sizeof( magma_in ),
+  ak_bckey_context_encrypt_cfb( &mkey, oc ? openssl_magma_in : magma_in, buf, sizeof( magma_in ),
                                      oc ? openssl_magma_ivcfb : magma_ivcfb, sizeof( magma_ivcfb ));
   printf("encrypted:\n");
   for( i = 0; i < 4; i++ ) {
-- 
GitLab