This patch/CL allows the user to configure the hash function used in the s2k key-stretching transformation during encryption. It fixes the current limitation in which SHA1 is hard-coded for that task. With this change, packet.Config.DefaultHash is used by default for the mode 3 s2k key-stretching that is currently used for encryption, but the user may choose any other valid openpgp hash by passing a new (possibly nil) pointer argument to s2k.Serialize(). Note that this change introduces an API change to s2k.Serialize() in the form of an additional argument. However, the s2k package is a low-level facility that (as far as I could tell) is used directly by go.crypto/openpgp only. The CL comes with an modified test to check the use and decoding of all the valid hash functions with regard to s2k.Serialize(). This patch should be followed by one that allows the user to configure the iteration count in mode 3 s2k, and it is a prerequisite for that forthcoming change. R=golang-codereviews, agl CC=agl, golang-codereviews https://golang.org/cl/160110045
219 lines
5.4 KiB
Go
219 lines
5.4 KiB
Go
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package s2k implements the various OpenPGP string-to-key transforms as
|
|
// specified in RFC 4800 section 3.7.1.
|
|
package s2k
|
|
|
|
import (
|
|
"crypto"
|
|
"hash"
|
|
"io"
|
|
"strconv"
|
|
|
|
"code.google.com/p/go.crypto/openpgp/errors"
|
|
)
|
|
|
|
// Config collects configuration parameters for s2k key-stretching
|
|
// transformatioms. A nil *Config is valid and results in all default
|
|
// values. Currently, Config is used only by the Serialize function in
|
|
// this package.
|
|
type Config struct {
|
|
// Hash is the default hash function to be used. If
|
|
// nil, SHA1 is used.
|
|
Hash crypto.Hash
|
|
}
|
|
|
|
func (c *Config) hash() crypto.Hash {
|
|
if c == nil || uint(c.Hash) == 0 {
|
|
// SHA1 is the historical default in this package.
|
|
return crypto.SHA1
|
|
}
|
|
|
|
return c.Hash
|
|
}
|
|
|
|
// Simple writes to out the result of computing the Simple S2K function (RFC
|
|
// 4880, section 3.7.1.1) using the given hash and input passphrase.
|
|
func Simple(out []byte, h hash.Hash, in []byte) {
|
|
Salted(out, h, in, nil)
|
|
}
|
|
|
|
var zero [1]byte
|
|
|
|
// Salted writes to out the result of computing the Salted S2K function (RFC
|
|
// 4880, section 3.7.1.2) using the given hash, input passphrase and salt.
|
|
func Salted(out []byte, h hash.Hash, in []byte, salt []byte) {
|
|
done := 0
|
|
var digest []byte
|
|
|
|
for i := 0; done < len(out); i++ {
|
|
h.Reset()
|
|
for j := 0; j < i; j++ {
|
|
h.Write(zero[:])
|
|
}
|
|
h.Write(salt)
|
|
h.Write(in)
|
|
digest = h.Sum(digest[:0])
|
|
n := copy(out[done:], digest)
|
|
done += n
|
|
}
|
|
}
|
|
|
|
// Iterated writes to out the result of computing the Iterated and Salted S2K
|
|
// function (RFC 4880, section 3.7.1.3) using the given hash, input passphrase,
|
|
// salt and iteration count.
|
|
func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
|
|
combined := make([]byte, len(in)+len(salt))
|
|
copy(combined, salt)
|
|
copy(combined[len(salt):], in)
|
|
|
|
if count < len(combined) {
|
|
count = len(combined)
|
|
}
|
|
|
|
done := 0
|
|
var digest []byte
|
|
for i := 0; done < len(out); i++ {
|
|
h.Reset()
|
|
for j := 0; j < i; j++ {
|
|
h.Write(zero[:])
|
|
}
|
|
written := 0
|
|
for written < count {
|
|
if written+len(combined) > count {
|
|
todo := count - written
|
|
h.Write(combined[:todo])
|
|
written = count
|
|
} else {
|
|
h.Write(combined)
|
|
written += len(combined)
|
|
}
|
|
}
|
|
digest = h.Sum(digest[:0])
|
|
n := copy(out[done:], digest)
|
|
done += n
|
|
}
|
|
}
|
|
|
|
// Parse reads a binary specification for a string-to-key transformation from r
|
|
// and returns a function which performs that transform.
|
|
func Parse(r io.Reader) (f func(out, in []byte), err error) {
|
|
var buf [9]byte
|
|
|
|
_, err = io.ReadFull(r, buf[:2])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
hash, ok := HashIdToHash(buf[1])
|
|
if !ok {
|
|
return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(buf[1])))
|
|
}
|
|
if !hash.Available() {
|
|
return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hash)))
|
|
}
|
|
h := hash.New()
|
|
|
|
switch buf[0] {
|
|
case 0:
|
|
f := func(out, in []byte) {
|
|
Simple(out, h, in)
|
|
}
|
|
return f, nil
|
|
case 1:
|
|
_, err = io.ReadFull(r, buf[:8])
|
|
if err != nil {
|
|
return
|
|
}
|
|
f := func(out, in []byte) {
|
|
Salted(out, h, in, buf[:8])
|
|
}
|
|
return f, nil
|
|
case 3:
|
|
_, err = io.ReadFull(r, buf[:9])
|
|
if err != nil {
|
|
return
|
|
}
|
|
count := (16 + int(buf[8]&15)) << (uint32(buf[8]>>4) + 6)
|
|
f := func(out, in []byte) {
|
|
Iterated(out, h, in, buf[:8], count)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
return nil, errors.UnsupportedError("S2K function")
|
|
}
|
|
|
|
// Serialize salts and stretches the given passphrase and writes the
|
|
// resulting key into key. It also serializes an S2K descriptor to
|
|
// w. The key stretching can be configured with c, which may be
|
|
// nil. In that case, sensible defaults will be used.
|
|
func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Config) error {
|
|
var buf [11]byte
|
|
buf[0] = 3 /* iterated and salted */
|
|
buf[1], _ = HashToHashId(c.hash())
|
|
salt := buf[2:10]
|
|
if _, err := io.ReadFull(rand, salt); err != nil {
|
|
return err
|
|
}
|
|
const count = 65536 // this is the default in gpg
|
|
buf[10] = 96 // 65536 iterations
|
|
if _, err := w.Write(buf[:]); err != nil {
|
|
return err
|
|
}
|
|
|
|
Iterated(key, c.hash().New(), passphrase, salt, count)
|
|
return nil
|
|
}
|
|
|
|
// hashToHashIdMapping contains pairs relating OpenPGP's hash identifier with
|
|
// Go's crypto.Hash type. See RFC 4880, section 9.4.
|
|
var hashToHashIdMapping = []struct {
|
|
id byte
|
|
hash crypto.Hash
|
|
name string
|
|
}{
|
|
{1, crypto.MD5, "MD5"},
|
|
{2, crypto.SHA1, "SHA1"},
|
|
{3, crypto.RIPEMD160, "RIPEMD160"},
|
|
{8, crypto.SHA256, "SHA256"},
|
|
{9, crypto.SHA384, "SHA384"},
|
|
{10, crypto.SHA512, "SHA512"},
|
|
{11, crypto.SHA224, "SHA224"},
|
|
}
|
|
|
|
// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
|
|
// hash id.
|
|
func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
|
|
for _, m := range hashToHashIdMapping {
|
|
if m.id == id {
|
|
return m.hash, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// HashIdToString returns the name of the hash function corresponding to the
|
|
// given OpenPGP hash id, or panics if id is unknown.
|
|
func HashIdToString(id byte) (name string, ok bool) {
|
|
for _, m := range hashToHashIdMapping {
|
|
if m.id == id {
|
|
return m.name, true
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
// HashIdToHash returns an OpenPGP hash id which corresponds the given Hash.
|
|
func HashToHashId(h crypto.Hash) (id byte, ok bool) {
|
|
for _, m := range hashToHashIdMapping {
|
|
if m.hash == h {
|
|
return m.id, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|