如何使用NaCl签署大型文件?

时间:2019-04-07 01:02:29

标签: file go cryptography signature

利用Go NaCl库(https://github.com/golang/crypto/tree/master/nacl/sign)的签名功能,如何对文件进行签名,尤其是对超过1GB的超大文件进行签名?大多数Internet搜索结果都与对切片或一小字节字节进行签名有关。

我可以想到两种方式:

  1. 以块方式(例如每次16k)遍历文件和流,然后将其输入到sign函数中。流输出被串联到签名证书中。为了进行验证,它是相反地进行的。
  2. 使用SHA(X)生成文件的阴影,然后对阴影输出进行签名。

2 个答案:

答案 0 :(得分:1)

对于签名非常大的文件(数GB及以上),使用标准签名功能的问题通常是运行时和易碎性。对于非常大的文件(或仅是慢速磁盘),可能需要花费数小时或更长时间才能从头到尾依次读取整个文件。

在这种情况下,您需要一种并行处理文件的方法。适于加密签名的常见方法之一是Merkle树哈希。它们使您可以将大文件拆分为较小的块,并行对它们进行哈希处理(产生“叶子哈希”),然后在树结构中进一步对这些哈希进行哈希处理以生成代表完整文件的根哈希。

一旦计算出此Merkle树根哈希,就可以对该根哈希签名。然后,可以使用签名的Merkle树根哈希值并行验证所有文件块,以及验证它们的顺序(基于树结构中叶哈希的位置)。

答案 1 :(得分:1)

NaCl的问题是您需要将整个消息放入RAM as per godoc

  

消息应该小,因为:1.整个消息需要保留在内存中以进行处理。 2.使用大消息会迫使小型计算机上的实现在不验证签名的情况下处理纯文本。这非常危险,并且不鼓励使用此API,但是使用过多消息大小的协议可能会提供某些实现,而没有其他选择。 3.通过使用适合数据缓存的消息可以提高性能。因此,应对大量数据进行分块处理,以使每个消息都较小。

但是,还有其他各种方法。他们中的大多数基本上都按照第一种方式进行了描述。基本上,您将文件内容复制到io.Writer中,该文件可以获取内容并计算哈希值-这是最有效的。

下面的代码很黑,但是您应该得到图片。 我达到了315MB / s的平均吞吐量。

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
}