bcrypt and null kdf done, work on ciphers next (then keytypes)

This commit is contained in:
2022-04-25 04:27:24 -04:00
parent 91d5e99404
commit ff3f8243d1
43 changed files with 755 additions and 423 deletions

11
kdf/bcrypt/consts.go Normal file
View File

@@ -0,0 +1,11 @@
package bcrypt
const (
Name string = "bcrypt"
// DefaultRounds is the default per OpenSSH, not per the bcrypt_pbkdf spec itself. It is recommended to use e.g. 100 rounds.
DefaultRounds uint32 = 16
// DefaultSaltLen is the default per OpenSSH, not per the bcrypt_pbkdf spec itself.
DefaultSaltLen int = 16
// DefaultKeyLen is suitable for AES256-CTR but may not be for others. TODO: revisit this and find something more flexible?
DefaultKeyLen uint32 = 48
)

246
kdf/bcrypt/funcs.go Normal file
View File

@@ -0,0 +1,246 @@
package bcrypt
import (
"bytes"
"crypto/rand"
"encoding/binary"
"github.com/dchest/bcrypt_pbkdf"
"r00t2.io/sshkeys/internal"
`r00t2.io/sshkeys/kdf/errs`
)
/*
Setup must be called before DeriveKey. It configures a .
If secret is nil or an empty byte slice, ErrNoSecret will be returned.
If salt is nil or an empty byte slice, one will be randomly generated of DefaultSaltLen length.
If rounds is 0, DefaultRounds will be used.
If keyLen is 0, DefaultKeyLen will be used.
*/
func (k *KDF) Setup(secret, salt []byte, rounds, keyLen uint32) (err error) {
if secret == nil || len(secret) == 0 {
err = errs.ErrNoSecret
return
}
if salt == nil || len(salt) == 0 {
salt = make([]byte, DefaultSaltLen)
if _, err = rand.Read(salt); err != nil {
return
}
}
if rounds == 0 {
rounds = DefaultRounds
}
if keyLen == 0 {
keyLen = DefaultKeyLen
}
k.secret = secret
k.salt = salt
k.rounds = rounds
k.keyLen = keyLen
k.hasSalt = true
k.hasRounds = true
return
}
/*
SetupAuto is used to provide out-of-band configuration if the KDF options were found via kdf.UnpackKDF.
You can test this by running KDF.AutoOK.
*/
func (k *KDF) SetupAuto(secret []byte, keyLen uint32) (err error) {
if !k.AutoOK() {
err = errs.ErrUnknownKdf
return
}
if keyLen == 0 {
keyLen = DefaultKeyLen
}
k.secret = secret
k.keyLen = keyLen
return
}
/*
DeriveKey returns the derived key from a configured bcrypt.KDF.
It must be called *after* Setup.
*/
func (k *KDF) DeriveKey() (key []byte, err error) {
if k.secret == nil {
err = errs.ErrNoSecret
return
}
if k.salt == nil {
err = errs.ErrNoSalt
return
}
if k.rounds == 0 {
err = errs.ErrNoRounds
return
}
if k.keyLen == 0 {
err = errs.ErrNoKeyLen
}
if k.key, err = bcrypt_pbkdf.Key(k.secret, k.salt, int(k.rounds), int(k.keyLen)); err != nil {
return
}
key = k.key
return
}
// Name returns bcrypt.Name.
func (k *KDF) Name() (name string) {
name = Name
return
}
// NameBytes returns the byte form of bcrypt.Name with leading bytecount allocator.
func (k *KDF) NameBytes() (name []byte) {
var nb []byte
var s = k.Name()
nb = []byte(s)
name = make([]byte, 4)
binary.BigEndian.PutUint32(name, uint32(len(nb)))
name = append(name, nb...)
return
}
// PackedBytes returns block 3.0 and recursed.
func (k *KDF) PackedBytes() (buf *bytes.Reader, err error) {
var rounds = make([]byte, 4)
var packer *bytes.Reader
var w = new(bytes.Buffer)
// block 3.0.0.0 and block 3.0.0.0.0
if packer, err = internal.ReadSizeBytes(k.salt, true); err != nil {
return
}
if _, err = packer.WriteTo(w); err != nil {
return
}
// block 3.0.0.1
binary.BigEndian.PutUint32(rounds, k.rounds)
if _, err = w.Write(rounds); err != nil {
return
}
// block 3.0
if buf, err = internal.ReadSizeBytes(w, true); err != nil {
return
}
return
}
// Rounds returns the number of rounds used in derivation.
func (k *KDF) Rounds() (rounds uint32) {
rounds = k.rounds
return
}
// Salt returns the salt bytes.
func (k *KDF) Salt() (salt []byte) {
salt = k.salt
return
}
/*
AutoOK returns true if a kdf.UnpackKDF call was able to fetch the bcrypt.KDF options successfully, in which case the caller may use bcrypt.KDF.SetupAuto.
If false, it will need to be manually configured via bcrypt.KDF.Setup.
*/
func (k *KDF) AutoOK() (ok bool) {
ok = true
if k.salt == nil || len(k.salt) == 0 {
ok = false
}
if k.rounds == 0 {
ok = false
}
return
}
// IsPlain indicates if this kdf.KDF actually does derivation (false) or not (true).
func (k *KDF) IsPlain() (plain bool) {
plain = false
return
}
/*
AddSalt adds a salt as parsed from kdf.UnpackKDF.
If a salt has already been set, a no-op will be performed.
If you want to use a different salt, you will need to create a new bcrypt.KDF.
*/
func (k *KDF) AddSalt(salt []byte) (err error) {
if salt == nil || len(salt) == 0 {
err = errs.ErrNoSalt
return
}
if k.hasSalt {
return
}
k.salt = salt
k.hasSalt = true
return
}
/*
AddRounds adds the rounds as parsed from kdf.UnpackKDF.
If the number of rounds has already been set, a no-op will be performed.
If you want to use a different number of rounds, you will need to create a new bcrypt.KDF.
*/
func (k *KDF) AddRounds(rounds uint32) (err error) {
if rounds == 0 {
err = errs.ErrNoRounds
return
}
if k.hasRounds {
return
}
k.rounds = rounds
k.hasRounds = true
return
}

25
kdf/bcrypt/types.go Normal file
View File

@@ -0,0 +1,25 @@
package bcrypt
/*
BcryptPbkdf combines bcrypt hashing algorithm with PBKDF2 key derivation.
(bcrypt) https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node1.html
(PBKDF2) https://datatracker.ietf.org/doc/html/rfc2898
http://www.tedunangst.com/flak/post/bcrypt-pbkdf
*/
type KDF struct {
// salt is used to salt the hash for each round in rounds.
salt []byte
// rounds controls how many iterations of salting/hashing is done.
rounds uint32
// keyLen is how long the derived key should be in bytes.
keyLen uint32
// secret is the "passphrase" used to seed the key creation.
secret []byte
// key is used to store the derived key.
key []byte
// hasSalt is true if a salt has been set.
hasSalt bool
// hasRounds is true if a number of rounds have been set.
hasRounds bool
}