syncthing/lib/signature/signature.go
Gusted 356c5055ad
lib/sha256: Remove it (#9643)
### Purpose

Remove the `lib/sha256` package, because it's no longer necessary. Go's
standard library now has the same performance and is on par with
`sha256-simd` since [Since Go
1.21](1a64574f42).
Therefore using `sha256-simd` has no benefits anymore.

ARM already has optimized sha256 assembly code since
7b8a7f8272,
`sha256-simd` published their results before that optimized assembly was
implemented,
f941fedda8.
The assembly looks very similar and the benchmarks in the Go commit
match that of `sha256-simd`.

This patch removes all of the related code of `lib/sha256` and makes
`crypto/sha256` the 'default'.

Benchmark of `sha256-simd` and `crypto/sha256`:
<details>

```
cpu: AMD Ryzen 5 3600X 6-Core Processor
                │  simd.txt   │               go.txt                │
                │   sec/op    │    sec/op     vs base               │
Hash/8Bytes-12    63.25n ± 1%    73.38n ± 1%  +16.02% (p=0.002 n=6)
Hash/64Bytes-12   98.73n ± 1%   105.30n ± 1%   +6.65% (p=0.002 n=6)
Hash/1K-12        567.2n ± 1%    572.8n ± 1%   +0.99% (p=0.002 n=6)
Hash/8K-12        4.062µ ± 1%    4.062µ ± 1%        ~ (p=0.396 n=6)
Hash/1M-12        512.1µ ± 0%    510.6µ ± 1%        ~ (p=0.485 n=6)
Hash/5M-12        2.556m ± 1%    2.564m ± 0%        ~ (p=0.093 n=6)
Hash/10M-12       5.112m ± 0%    5.127m ± 0%        ~ (p=0.093 n=6)
geomean           13.82µ         14.27µ        +3.28%

                │   simd.txt   │               go.txt                │
                │     B/s      │     B/s       vs base               │
Hash/8Bytes-12    120.6Mi ± 1%   104.0Mi ± 1%  -13.81% (p=0.002 n=6)
Hash/64Bytes-12   618.2Mi ± 1%   579.8Mi ± 1%   -6.22% (p=0.002 n=6)
Hash/1K-12        1.682Gi ± 1%   1.665Gi ± 1%   -0.98% (p=0.002 n=6)
Hash/8K-12        1.878Gi ± 1%   1.878Gi ± 1%        ~ (p=0.310 n=6)
Hash/1M-12        1.907Gi ± 0%   1.913Gi ± 1%        ~ (p=0.485 n=6)
Hash/5M-12        1.911Gi ± 1%   1.904Gi ± 0%        ~ (p=0.093 n=6)
Hash/10M-12       1.910Gi ± 0%   1.905Gi ± 0%        ~ (p=0.093 n=6)
geomean           1.066Gi        1.032Gi        -3.18%
```

</details>


### Testing

Compiled and tested on Linux.

### Documentation

https://github.com/syncthing/docs/pull/874
2024-08-10 12:58:20 +01:00

204 lines
4.5 KiB
Go

// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// Package signature provides simple methods to create and verify signatures
// in PEM format.
package signature
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"crypto/x509"
"encoding/asn1"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"github.com/syncthing/syncthing/lib/rand"
)
// GenerateKeys returns a new key pair, with the private and public key
// encoded in PEM format.
func GenerateKeys() (privKey []byte, pubKey []byte, err error) {
// Generate a new key pair
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
if err != nil {
return nil, nil, err
}
// Marshal the private key
bs, err := x509.MarshalECPrivateKey(key)
if err != nil {
return nil, nil, err
}
// Encode it in PEM format
privKey = pem.EncodeToMemory(&pem.Block{
Type: "EC PRIVATE KEY",
Bytes: bs,
})
// Marshal the public key
bs, err = x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
return nil, nil, err
}
// Encode it in PEM format
pubKey = pem.EncodeToMemory(&pem.Block{
Type: "EC PUBLIC KEY",
Bytes: bs,
})
return
}
// Sign computes the hash of data and signs it with the private key, returning
// a signature in PEM format.
func Sign(privKeyPEM []byte, data io.Reader) ([]byte, error) {
// Parse the private key
key, err := loadPrivateKey(privKeyPEM)
if err != nil {
return nil, err
}
// Hash the reader data
hash, err := hashReader(data)
if err != nil {
return nil, err
}
// Sign the hash
r, s, err := ecdsa.Sign(rand.Reader, key, hash)
if err != nil {
return nil, err
}
// Marshal the signature using ASN.1
sig, err := marshalSignature(r, s)
if err != nil {
return nil, err
}
// Encode it in a PEM block
bs := pem.EncodeToMemory(&pem.Block{
Type: "SIGNATURE",
Bytes: sig,
})
return bs, nil
}
// Verify computes the hash of data and compares it to the signature using the
// given public key. Returns nil if the signature is correct.
func Verify(pubKeyPEM []byte, signature []byte, data io.Reader) error {
// Parse the public key
key, err := loadPublicKey(pubKeyPEM)
if err != nil {
return err
}
// Parse the signature
block, _ := pem.Decode(signature)
if block == nil || block.Bytes == nil {
return errors.New("unsupported signature format")
}
r, s, err := unmarshalSignature(block.Bytes)
if err != nil {
return err
}
// Compute the hash of the data
hash, err := hashReader(data)
if err != nil {
return err
}
// Verify the signature
if !ecdsa.Verify(key, hash, r, s) {
return errors.New("incorrect signature")
}
return nil
}
// hashReader returns the SHA256 hash of the reader
func hashReader(r io.Reader) ([]byte, error) {
h := sha256.New()
if _, err := io.Copy(h, r); err != nil {
return nil, err
}
hash := []byte(fmt.Sprintf("%x", h.Sum(nil)))
return hash, nil
}
// loadPrivateKey returns the ECDSA private key structure for the given PEM
// data.
func loadPrivateKey(bs []byte) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode(bs)
return x509.ParseECPrivateKey(block.Bytes)
}
// loadPublicKey returns the ECDSA public key structure for the given PEM
// data.
func loadPublicKey(bs []byte) (*ecdsa.PublicKey, error) {
// Decode and parse the public key PEM block
block, _ := pem.Decode(bs)
if block == nil || block.Bytes == nil {
return nil, errors.New("unsupported public key format")
}
intf, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
// It should be an ECDSA public key
pk, ok := intf.(*ecdsa.PublicKey)
if !ok {
return nil, errors.New("unsupported public key format")
}
return pk, nil
}
// A wrapper around the signature integers so that we can marshal and
// unmarshal them.
type signature struct {
R, S *big.Int
}
// marhalSignature returns ASN.1 encoded bytes for the given integers,
// suitable for PEM encoding.
func marshalSignature(r, s *big.Int) ([]byte, error) {
sig := signature{
R: r,
S: s,
}
bs, err := asn1.Marshal(sig)
if err != nil {
return nil, err
}
return bs, nil
}
// unmarshalSignature returns the R and S integers from the given ASN.1
// encoded signature.
func unmarshalSignature(sig []byte) (r *big.Int, s *big.Int, err error) {
var ts signature
_, err = asn1.Unmarshal(sig, &ts)
if err != nil {
return nil, nil, err
}
return ts.R, ts.S, nil
}