Как использовать NaCl для подписи большого файла? - PullRequest
2 голосов
/ 07 апреля 2019

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

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

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

Ответы [ 2 ]

1 голос
/ 08 апреля 2019

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

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

Однако существуют различные другие методы.Большинство из них в основном делают то, что вы описали первым способом.Вы в основном копируете содержимое файла в 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
}
1 голос
/ 08 апреля 2019

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

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

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

...