Как использовать NaCl для подписи большого файла?

Учитывая возможность подписи из библиотеки Go NaCl (https://github.com/golang/crypto/tree/master/nacl/sign), как подписать файл, особенно очень большой файл размером более 1 ГБ? Большинство результатов поиска в Интернете посвящены подписи фрагмента или небольшого массива байтов.

Я могу думать о 2 способах:

  1. Прокручивайте файл и поток в блочном режиме (например, 16 КБ каждый раз), а затем загружайте его в функцию знака. Потоковые выходные данные объединяются в сертификат подписи. Для проверки это делается в обратном порядке.
  2. Используйте SHA(X) для создания shasum файла, а затем подпишите вывод shasum.

Добро пожаловать в crypto.stackexchange. Кажется, это вопрос программирования, а вопросы программирования здесь не по теме, даже если они используют криптографию. Правильное место для вопросов по программированию — stackoverflow; Я могу перенести это туда для вас.

Ella Rose 07.04.2019 03:08

Хороший пример austburn.me/blog/golang-server.html

MikiBelavista 07.04.2019 09:36

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

Natanael 07.04.2019 14:41

Из godoc: «Сообщения должны быть маленькими, потому что: 1. Все сообщение должно храниться в памяти для обработки. 2. Использование больших сообщений вынуждает реализации на небольших машинах обрабатывать открытый текст без проверки подписи. Это очень опасно, и этот API не одобряет это, но протокол, использующий чрезмерные размеры сообщений, может оставить некоторые реализации без другого выбора. 3. Производительность можно улучшить, работая с сообщениями, которые помещаются в кэши данных. Таким образом, большие объемы данных должны быть разбиты на части, чтобы каждое сообщение было небольшим».

Markus W Mahlberg 07.04.2019 22:48

@Натанаэль, спасибо. Можете ли вы преобразовать свой комментарий в ответ, пожалуйста? Это именно то, что я ищу. Я нашел нужные мне примеры: github.com/cbergoon/merkletree/blob/master/merkle_tree.goen.wikipedia.org/wiki/Merkle_tree. Я отмечу это как ответ.

Holloway Kean Ho 07.04.2019 23:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
337
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

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

В таких случаях вам нужен способ параллельной обработки файла. Одним из распространенных способов сделать это, который подходит для криптографических подписей, являются хэши дерева Меркла. Они позволяют разбить большой файл на более мелкие фрагменты, хэшировать их параллельно (создавая «хэш-листы»), а затем дополнительно хешировать эти хэши в древовидной структуре для создания корневого хэша, представляющего полный файл.

После того, как вы вычислили этот корневой хэш дерева Меркла, вы можете подписать этот корневой хэш. Затем становится возможным использовать подписанный хэш корня дерева Меркла для параллельной проверки всех фрагментов файла, а также для проверки их порядка (на основе позиций хэшей листьев в древовидной структуре).

Проблема с NaCl в том, что вам нужно поместить все сообщение в ОЗУ, согласно годоку:

Messages should be small because: 1. The whole message needs to be held in memory to be processed. 2. Using large messages pressures implementations on small machines to process plaintext without verifying the signature. This is very dangerous, and this API discourages it, but a protocol that uses excessive message sizes might present some implementations with no other choice. 3. Performance may be improved by working with messages that fit into data caches. Thus large amounts of data should be chunked so that each message is small.

Однако существуют различные другие методы. Большинство из них в основном делают то, что вы описали первым способом. Вы в основном копируете содержимое файла в io.Writer, который берет содержимое и вычисляет хеш-сумму — это наиболее эффективно.

Код ниже довольно сильно взломан, но вы должны получить представление. Я добился средней пропускной способности 315 МБ/с.

package main

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/sha256"
    "flag"
    "fmt"
    "io"
    "log"
    "math/big"
    "os"
    "time"
)

var filename = flag.String("file", "", "file to sign")

func main() {
    flag.Parse()

    if *filename == "" {
        log.Fatal("file can not be empty")
    }

    f, err := os.Open(*filename)
    if err != nil {
        log.Fatalf("Error opening '%s': %s", *filename, err)
    }
    defer f.Close()

    start := time.Now()
    sum, n, err := hash(f)
    duration := time.Now().Sub(start)
    log.Printf("Hashed %s (%d bytes)in %s to %x", *filename, n, duration, sum)

    log.Printf("Average: %.2f MB/s", (float64(n)/1000000)/duration.Seconds())

    r, s, err := sign(sum)
    if err != nil {
        log.Fatalf("Error creatig signature: %s", err)
    }

    log.Printf("Signature: (0x%x,0x%x)\n", r, s)

}

func sign(sum []byte) (*big.Int, *big.Int, error) {
    priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    if err != nil {
        log.Printf("Error creating private key: %s", err)
    }

    return ecdsa.Sign(rand.Reader, priv, sum[:])

}

func hash(f *os.File) ([]byte, int64, error) {
    var (
        hash []byte
        n    int64
        err  error
    )

    h := sha256.New()

    // This is where the magic happens.
    // We use the efficient io.Copy to feed the contents
    // of the file into the hash function.
    if n, err = io.Copy(h, f); err != nil {
        return nil, n, fmt.Errorf("Error creating hash: %s", err)
    }
    hash = h.Sum(nil)
    return hash, n, nil
}

Спасибо за ответ. Действительно, сообщение должно быть в оперативной памяти. Алгоритм дерева Маркуса создаст единый корневой shasum, с которым я затем смогу подписать (меньше, меньше нагрузки на процессор из-за знака NaCL, и, что самое приятное, также облегчает потоковую передачу по сети). Я пойду с этим направлением.

Holloway Kean Ho 08.04.2019 00:14

@HollowayKeanHo Почему это должно быть в оперативной памяти? Вы можете скопировать любой io.Reader в хэш.

Markus W Mahlberg 08.04.2019 00:16

окончательный ввод (подпись shasum) в NaCL должен быть в ОЗУ для более быстрой обработки.

Holloway Kean Ho 08.04.2019 00:26

@HollowayKeanHo Прочтите код. Шасум находится в оперативной памяти.

Markus W Mahlberg 10.04.2019 09:25

Другие вопросы по теме