mirror of
https://github.com/Luzifer/cloudkeys-go.git
synced 2024-11-10 15:10:05 +00:00
Knut Ahlers
a1df72edc5
commitf0db1ff1f8
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:19:56 2017 +0100 Mark option as deprecated Signed-off-by: Knut Ahlers <knut@ahlers.me> commit9891df2a16
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:11:56 2017 +0100 Fix: Typo Signed-off-by: Knut Ahlers <knut@ahlers.me> commit836006de64
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 12:04:20 2017 +0100 Add new dependencies Signed-off-by: Knut Ahlers <knut@ahlers.me> commitd64fee60c8
Author: Knut Ahlers <knut@ahlers.me> Date: Sun Dec 24 11:55:52 2017 +0100 Replace insecure password hashing Prior this commit passwords were hashed with a static salt and using the SHA1 hashing function. This could lead to passwords being attackable in case someone gets access to the raw data stored inside the database. This commit introduces password hashing using bcrypt hashing function which addresses this issue. Old passwords are not automatically re-hashed as they are unknown. Replacing the old password scheme is not that easy and needs #10 to be solved. Therefore the old hashing scheme is kept for compatibility reason. Signed-off-by: Knut Ahlers <knut@ahlers.me> Signed-off-by: Knut Ahlers <knut@ahlers.me> closes #14 closes #15
273 lines
7 KiB
Go
273 lines
7 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 "golang.org/x/crypto/openpgp/s2k"
|
|
|
|
import (
|
|
"crypto"
|
|
"hash"
|
|
"io"
|
|
"strconv"
|
|
|
|
"golang.org/x/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
|
|
// S2KCount is only used for symmetric encryption. It
|
|
// determines the strength of the passphrase stretching when
|
|
// the said passphrase is hashed to produce a key. S2KCount
|
|
// should be between 1024 and 65011712, inclusive. If Config
|
|
// is nil or S2KCount is 0, the value 65536 used. Not all
|
|
// values in the above range can be represented. S2KCount will
|
|
// be rounded up to the next representable value if it cannot
|
|
// be encoded exactly. When set, it is strongly encrouraged to
|
|
// use a value that is at least 65536. See RFC 4880 Section
|
|
// 3.7.1.3.
|
|
S2KCount int
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
func (c *Config) encodedCount() uint8 {
|
|
if c == nil || c.S2KCount == 0 {
|
|
return 96 // The common case. Correspoding to 65536
|
|
}
|
|
|
|
i := c.S2KCount
|
|
switch {
|
|
// Behave like GPG. Should we make 65536 the lowest value used?
|
|
case i < 1024:
|
|
i = 1024
|
|
case i > 65011712:
|
|
i = 65011712
|
|
}
|
|
|
|
return encodeCount(i)
|
|
}
|
|
|
|
// encodeCount converts an iterative "count" in the range 1024 to
|
|
// 65011712, inclusive, to an encoded count. The return value is the
|
|
// octet that is actually stored in the GPG file. encodeCount panics
|
|
// if i is not in the above range (encodedCount above takes care to
|
|
// pass i in the correct range). See RFC 4880 Section 3.7.7.1.
|
|
func encodeCount(i int) uint8 {
|
|
if i < 1024 || i > 65011712 {
|
|
panic("count arg i outside the required range")
|
|
}
|
|
|
|
for encoded := 0; encoded < 256; encoded++ {
|
|
count := decodeCount(uint8(encoded))
|
|
if count >= i {
|
|
return uint8(encoded)
|
|
}
|
|
}
|
|
|
|
return 255
|
|
}
|
|
|
|
// decodeCount returns the s2k mode 3 iterative "count" corresponding to
|
|
// the encoded octet c.
|
|
func decodeCount(c uint8) int {
|
|
return (16 + int(c&15)) << (uint32(c>>4) + 6)
|
|
}
|
|
|
|
// 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 := decodeCount(buf[8])
|
|
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
|
|
}
|
|
encodedCount := c.encodedCount()
|
|
count := decodeCount(encodedCount)
|
|
buf[10] = encodedCount
|
|
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.
|
|
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
|
|
}
|