Методологии
July 5

Технико-экономическое обоснование (ТЭО) в жизни инженера

Данную статью я бы начал с простого ответа на вопрос читателя: "на кой мне про это знать?"

Любой инженер во время работы сталкивается с ситуацией, требующей от него принять решение в пользу той или иной технологии, методики, способа.

Let's help crypto housekeeper Kuza make the right choice.

Современный инженер, я сужу исходя из собственной насмотренности, использует интуицию в качестве одного из сильнейших своих "профессиональных навыков".

В статье речь пойдёт именно про подход как осознанно и объективно принять выбор.

Задача

В качестве задачи предлагаю сделать выбор быстрого алгоритма шифрования для обеспечения целостности данных за счёт цифровой подписи с минимальной утилизацией ресурсов для языка программирования golang

Возможные варианты

На рынке существует несколько решений, от всем известных, до государством одобряемых.

Перечислим их:

Критерии оценки

Методика называется технико-экономическое обоснование не спроста.

Это означает, что нужно придумать список технических и экономических критериев для оценки.

Технические критерии

| Наименование         | Вес | Обоснование веса                                                            |
|----------------------|-----|-----------------------------------------------------------------------------|
| Утилизация CPU       | 3   | Самая дорогая стоимость масштабирования                                     |
| Утилизация RAM       | 2   | Средняя стоимость масштабирования                                           |
| Наличие зависимостей | 2   | Чем больше зависимостей, тем выше вероятность внешнего дефекта и уязвимости |

Почему вес утилизации CPU - 3?

Это обусловлено стоимостью увеличения производительности CPU, на текущий момент она самая высокая.

Почему вес утилизации RAM - 2?

Стоимость расширения же RAM уступает по цене улучшению CPU; масштабирование всё ещё не бесплатно.

Почему наличие зависимостей - плохо?

Говоря языком математики (теорией вероятностей), то любое стороннее решение - это некая вероятность дефекта P(defect), допущенного сторонним разработчиком, плюс вероятность уязвимости P(vulnerability).

Грубо говоря, каждая прямая зависимость обладает вероятностью, что вам, как инженеру, принявшему решение использовать ту или иную зависимость, придётся нести ответственность за внешнего разработчика:

P(dependency) = P(defect) + P(vulnerability)

Если зависимостей несколько, то вероятностью является сумма каждой зависимости:

P(dependency) = P(dependency 1) + P(dependency 2) + ... + P(dependency N)

Как оценить эти вероятности - это отдельная большая статья (пишите в комментарии, интересно ли будет про такое читать).

Экономические критерии

| Наименование            | Вес | Обоснование веса                          |
|-------------------------|-----|-------------------------------------------|
| Стоимость владения      | 2   | Для сокрашения статических затрат         |
| Стоимость сопровождения | 3   | Для сокрашения динамических затрат        |
| Стоимость внедрения     | 1   | Каждое решение обладает разной сложностью |

Почему вес стоимости владения - 2?

Сперва определим, что это такое: это та стоимость, которую нужно выплатить за использование. В нашем списке речь идёт про открытые алгоритмы шифрования, стоимость которых пока равна нулю.

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

Почему вес стоимости сопровождения - 3?

Стоимость зарплат инженеров по эксплуатации и разработке чаще всего заметно выше лицензионных сборов, поэтому вес выше предыдущего.

Почему вес стоимости внедрения - 1?

Разработка ПО для компании на базе любого из перечисленных решений будет стоить какого-то количества человеко-часов, которые никогда не бывают бесплатными.

Эксперимент

Что будет выполнять

Алгоритм должен взять тестовый набор данных, сгенерировать для него подпись и её проверить для указанных данных.

Детали

Для проведения эксперимента будут использоваться базовые инструменты языка программирования.

Также будут добавлены следующие флаги для запуска тестов:

  • test.benchmem - для отображения утилизации RAM
  • test.benchtime=5s - для увеличения эксперимента с 1 секунды до 5

Эксперимент будет проводиться на железе:

  • ОС - MacOS
  • CPU - Apple M3 Max (ARM64)
  • RAM - 48 Gb

Benchmark для RSA с 2048 битным ключём

Почему 2048 битный ключ?

Алгоритм шифрования RSA достаточно старый, обладает определённым набором криптографических слабостей. Для того, чтобы его справедливо уравнять с остальными, ключ шифрования необходимо увеличить. Чтобы сравнение было хоть сколько-то адекватно, увеличивать ключ до бесконечности мы не можем. На текущий момент двухкилобитный ключ вполне достаточен.

Конечно, для принятия более взвешенного решения, нужно бы отдельно провести анализ размера ключа, для обеспечения более объективного выбора. Я отказался от этого в пользу сохранения читаемости текста.
package test

import (
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"log"
	"testing"
)

var msg = []byte("Let's help crypto housekeeper Kuza make the right choice")

func BenchmarkRSA2048(b *testing.B) {
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Fatal(err)
	}

	pub := &priv.PublicKey

	var sign []byte

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sign, err = rsa.SignPKCS1v15(rand.Reader, priv, crypto.Hash(0), msg)
		if err != nil {
			log.Fatal(err)
		}

		err = rsa.VerifyPKCS1v15(pub, crypto.Hash(0), msg, sign)
		if err != nil {
			log.Fatal(err)
		}
	}
}

Что важно отметить:

  • код максимально простой
  • данный алгоритм входит в базовые зависимости языка программирования
  • данные не хешируются для создания подписи и её верификации

Результат:

goos: darwin
goarch: arm64
cpu: Apple M3 Max
BenchmarkRSA2048-10    1,308    923,656 ns/op

Benchmark для Ed25519

package test

import (
	"crypto"
	"crypto/ed25519"
	"log"
	"testing"
)

var msg = []byte("Let's help crypto housekeeper Kuza make the right choice")

func BenchmarkEd25519(b *testing.B) {
	pub, priv, err := ed25519.GenerateKey(nil)
	if err != nil {
		log.Fatal(err)
	}

	var sign []byte

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sign, err = priv.Sign(nil, msg, &ed25519.Options{})
		if err != nil {
			log.Fatal(err)
		}

		err = ed25519.VerifyWithOptions(pub, msg, sign, &ed25519.Options{})
		if err != nil {
			log.Fatal(err)
		}
	}
}

Что важно отметить:

  • код максимально простой
  • данный алгоритм входит в базовые зависимости языка программирования
  • данные не хешируются для создания подписи и её верификации

Результат:

goos: darwin
goarch: arm64
cpu: Apple M3 Max
BenchmarkEd25519-10    15,927    73,565 ns/op

Benchmark для ГОСТ Р 34.10

package test

import (
	"crypto"
	"crypto/rand"
	"log"
	"testing"

	"github.com/ddulesov/gogost/gost3410"
)

var msg = []byte("Let's help crypto housekeeper Kuza make the right choice")

func BenchmarkGOST3410(b *testing.B) {
	curve := gost3410.CurveIdtc26gost341012512paramSetA()

	priv, err := gost3410.GenPrivateKey(curve, gost3410.Mode2012, rand.Reader)
	if err != nil {
		b.Fatal(err)
	}

	var pub *gost3410.PublicKey

	pub, err = priv.PublicKey()
	if err != nil {
		b.Fatal(err)
	}

	var sign []byte

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		sign, err = priv.SignDigest(msg, rand.Reader)
		if err != nil {
			b.Fatal(err)
		}

		_, err = pub.VerifyDigest(msg, sign)
		if err != nil {
			b.Fatal(err)
		}
	}
}

Что важно отметить:

  • в государственных структурах до сих пор не научились давать адекватные названия
  • сигнатура зависимости местами далека от интуитивно понятной
  • данный алгоритм является внешним, поставляется отдельной зависимостью
  • данные не хешируются для создания подписи и её верификации

Результат:

goos: darwin
goarch: arm64
cpu: Apple M3 Max
BenchmarkGOST3410-10    80    12,692,735 ns/op

Сводная таблица результатов

| Эксперимент | Время | Итераций | Время одной операции | Утилизация RAM | Аллокаций RAM |
|-------------|-------|----------|----------------------|----------------|---------------|
| RSA2048     | 5 s   | 6415     | 900,552 ns           | 1,888          | 11            |
| Ed25519     | 5 s   | 81256    | 74,015 ns            | 88             | 2             |
| GOST3410    | 5 s   | 471      | 12,407,072 ns        | 3,981,786      | 48,399        |

Пояснения:

  • Время - это период прохождения эксперимент
  • Итераций - число успешных операций подписи и её проверки
  • Время одной операции - это необходимый период, требуемый железу на выполнение одного цикла генерации подписи и её проверки
  • Утилизация RAM - сколько байт памяти будет выделено для выполнения одного цикла
  • Аллокаций RAM - сколько выделений памяти будет сделано для выполнения одного цикла

Интерпретация результатов

| Оценка                             | Вес | RSA2048 | Ed25519 | GOST3410 |
|------------------------------------|-----|---------|---------|----------|
| Утилизация CPU                     | 3   | M       | S       | L        |
| Утилизация RAM                     | 2   | M       | S       | L        |
| Наличие зависимостей у решения     | 2   | -       | -       | +        |
| Стоимость владения технологией     | 2   | 0       | 0       | 0        |
| Стоимость сопровождения технологии | 3   | S       | S       | S        |
| Стоимость внедрения                | 1   | S       | S       | S        |

Легенда:

  • L - высокая (значение - 3)
  • M - средняя (значение - 2)
  • S - низкая (значение - 1)

Пояснения:

  • Самая высокая утилизация CPU у ГОСТ Р 34.10, времени на одну операцию требуется разительно больше, чем остальным; на втором месте идёт RSA; лидером является - Ed25519
  • Самая высокая утилизация RAM и высокое число аллокаций также у ГОСТ Р 34.10; на втором месте - RSA; лидером является - Ed25519
  • ГОСТ Р 34.10 выполнено отдельной зависимостью, остальные решения поддерживаются языком программирования
  • Для использования всех трёх алгоритмов не потребуется никаких выплат - все они поставляются под свободными лицензиями
  • Как показали примеры кода выше, реализация нужного алгоритма на базе каждого из решений не потребует больших интеллектуальных вложений, следовательно, стоимости сопровождения и внедрения будут низкими

Расчёты

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

Предлагаю использовать вот такую достаточно простую для понимания формулу:

score = сpu_util * cpu_util_weight 
      + ram_util * ram_util_weight
      + is_dep * is_dep_weight
      + stat_cost * stat_cost_weight
      + dyn_cost * stat_cost_weight
      + integ_cost * integ_cost_weight

Что есть что:

  • сpu_util - утилизация CPU
  • ram_util - утилизация RAM
  • is_dep - поставляется отдельной зависимостью или нет
  • stat_cost - величина статической стоимости
  • dyn_cost - величина динамической стоимости
  • integ_cost - величина стоимости внедрения
  • _weight - это веса из таблиц с критериями оценки
rsa_score = 2*3 + 2*2 + 0*2 + 0*2 + 1*3 + 1*1 = 14

ed25519_score = 1*3 + 1*2 + 0*2 + 0*2 + 1*3 + 1*1 = 9

gost3410_score = 3*3 + 3*2 + 1*2 + 0*2 + 1*3 + 1*1 = 21

Выводы

Лучшее решение обладает меньшей оценкой. Из приведённых расчётов к такому относятся Ed25519.

ГОСТ Р 34.10, сравнивая со всеми остальными решениями, обладает наибольшей утилизацией, как CPU, так и RAM.
Стоимость использования такого решения будет оставаться высокой.

RSA c 2048 битным ключом утилизирует аппаратные ресурсы заметно слабее, но на порядок выше, чем это делает Ed25519.

PS

Прочитав эту статью, надеюсь, вы начнёте чаще использовать объективный выбор и его же требовать от руководителей, что должно сократить число богемы в мире ИТ 😎