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
165 lines
4.6 KiB
Go
165 lines
4.6 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 packet
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/cipher"
|
|
"io"
|
|
"strconv"
|
|
|
|
"code.google.com/p/go.crypto/openpgp/errors"
|
|
"code.google.com/p/go.crypto/openpgp/s2k"
|
|
)
|
|
|
|
// This is the largest session key that we'll support. Since no 512-bit cipher
|
|
// has even been seriously used, this is comfortably large.
|
|
const maxSessionKeySizeInBytes = 64
|
|
|
|
// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
|
|
// 4880, section 5.3.
|
|
type SymmetricKeyEncrypted struct {
|
|
CipherFunc CipherFunction
|
|
Encrypted bool
|
|
Key []byte // Empty unless Encrypted is false.
|
|
s2k func(out, in []byte)
|
|
encryptedKey []byte
|
|
}
|
|
|
|
const symmetricKeyEncryptedVersion = 4
|
|
|
|
func (ske *SymmetricKeyEncrypted) parse(r io.Reader) (err error) {
|
|
// RFC 4880, section 5.3.
|
|
var buf [2]byte
|
|
_, err = readFull(r, buf[:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
if buf[0] != symmetricKeyEncryptedVersion {
|
|
return errors.UnsupportedError("SymmetricKeyEncrypted version")
|
|
}
|
|
ske.CipherFunc = CipherFunction(buf[1])
|
|
|
|
if ske.CipherFunc.KeySize() == 0 {
|
|
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
|
|
}
|
|
|
|
ske.s2k, err = s2k.Parse(r)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
encryptedKey := make([]byte, maxSessionKeySizeInBytes)
|
|
// The session key may follow. We just have to try and read to find
|
|
// out. If it exists then we limit it to maxSessionKeySizeInBytes.
|
|
n, err := readFull(r, encryptedKey)
|
|
if err != nil && err != io.ErrUnexpectedEOF {
|
|
return
|
|
}
|
|
err = nil
|
|
if n != 0 {
|
|
if n == maxSessionKeySizeInBytes {
|
|
return errors.UnsupportedError("oversized encrypted session key")
|
|
}
|
|
ske.encryptedKey = encryptedKey[:n]
|
|
}
|
|
|
|
ske.Encrypted = true
|
|
|
|
return
|
|
}
|
|
|
|
// Decrypt attempts to decrypt an encrypted session key. If it returns nil,
|
|
// ske.Key will contain the session key.
|
|
func (ske *SymmetricKeyEncrypted) Decrypt(passphrase []byte) error {
|
|
if !ske.Encrypted {
|
|
return nil
|
|
}
|
|
|
|
key := make([]byte, ske.CipherFunc.KeySize())
|
|
ske.s2k(key, passphrase)
|
|
|
|
if len(ske.encryptedKey) == 0 {
|
|
ske.Key = key
|
|
} else {
|
|
// the IV is all zeros
|
|
iv := make([]byte, ske.CipherFunc.blockSize())
|
|
c := cipher.NewCFBDecrypter(ske.CipherFunc.new(key), iv)
|
|
c.XORKeyStream(ske.encryptedKey, ske.encryptedKey)
|
|
ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
|
|
if ske.CipherFunc.blockSize() == 0 {
|
|
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(ske.CipherFunc)))
|
|
}
|
|
ske.CipherFunc = CipherFunction(ske.encryptedKey[0])
|
|
ske.Key = ske.encryptedKey[1:]
|
|
if len(ske.Key)%ske.CipherFunc.blockSize() != 0 {
|
|
ske.Key = nil
|
|
return errors.StructuralError("length of decrypted key not a multiple of block size")
|
|
}
|
|
}
|
|
|
|
ske.Encrypted = false
|
|
return nil
|
|
}
|
|
|
|
// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w. The
|
|
// packet contains a random session key, encrypted by a key derived from the
|
|
// given passphrase. The session key is returned and must be passed to
|
|
// SerializeSymmetricallyEncrypted.
|
|
// If config is nil, sensible defaults will be used.
|
|
func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) {
|
|
cipherFunc := config.Cipher()
|
|
keySize := cipherFunc.KeySize()
|
|
if keySize == 0 {
|
|
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
|
|
}
|
|
|
|
s2kBuf := new(bytes.Buffer)
|
|
keyEncryptingKey := make([]byte, keySize)
|
|
// s2k.Serialize salts and stretches the passphrase, and writes the
|
|
// resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
|
|
err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash()})
|
|
if err != nil {
|
|
return
|
|
}
|
|
s2kBytes := s2kBuf.Bytes()
|
|
|
|
packetLength := 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
|
|
err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var buf [2]byte
|
|
buf[0] = symmetricKeyEncryptedVersion
|
|
buf[1] = byte(cipherFunc)
|
|
_, err = w.Write(buf[:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
_, err = w.Write(s2kBytes)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
sessionKey := make([]byte, keySize)
|
|
_, err = io.ReadFull(config.Random(), sessionKey)
|
|
if err != nil {
|
|
return
|
|
}
|
|
iv := make([]byte, cipherFunc.blockSize())
|
|
c := cipher.NewCFBEncrypter(cipherFunc.new(keyEncryptingKey), iv)
|
|
encryptedCipherAndKey := make([]byte, keySize+1)
|
|
c.XORKeyStream(encryptedCipherAndKey, buf[1:])
|
|
c.XORKeyStream(encryptedCipherAndKey[1:], sessionKey)
|
|
_, err = w.Write(encryptedCipherAndKey)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
key = sessionKey
|
|
return
|
|
}
|