passforios-gopenpgp/crypto/symmetrickey.go
wussler e65ed17b41
Refactor api (#6)
* Refactor library, remove duplicates

* Rebuild structure to use Messages and Signature models

* Use PGPSplitMessage

* Remove signature model

* Various fixes

* Add helpers with tests

* Fixes, add some docs, add tests

* Add attachment helpers

* Add helpers Symmetric encryption

* Edit docs + examples

* Rename kr to keyRing

* Various fixes for documentation

* Edit JSON handling functions, add decrypt keyring via token

* Add proposal changes doc

* Fix CI

* Drop *Message functions, join CleartextMessage and BinaryMessage

* Change canonicalization and trimming only to text signatures

* Add cleartextsignature, detach signature from message model, move helpers

* Documentation, remove optional parameters

* Move verification to separate model

* Don't return message in VerifyDetached

* Update table of contents in readme

* Appease golint

* Run go fmt

* Rename Encrypt/DecryptMessageWithPassword to ..WithToken

These functions shouldn't be used with user-provided passwords,
as they don't do any key-stretching.

* Change key generation usernames
2019-06-03 17:00:01 +02:00

224 lines
5.3 KiB
Go

package crypto
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"github.com/ProtonMail/gopenpgp/constants"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
)
// SymmetricKey stores a decrypted session key.
type SymmetricKey struct {
// The decrypted binary session key.
Key []byte
// The symmetric encryption algorithm used with this key.
Algo string
}
var symKeyAlgos = map[string]packet.CipherFunction{
constants.ThreeDES: packet.Cipher3DES,
constants.TripleDES: packet.Cipher3DES,
constants.CAST5: packet.CipherCAST5,
constants.AES128: packet.CipherAES128,
constants.AES192: packet.CipherAES192,
constants.AES256: packet.CipherAES256,
}
// GetCipherFunc returns the cipher function corresponding to the algorithm used
// with this SymmetricKey.
func (symmetricKey *SymmetricKey) GetCipherFunc() packet.CipherFunction {
cf, ok := symKeyAlgos[symmetricKey.Algo]
if ok {
return cf
}
panic("gopenpgp: unsupported cipher function: " + symmetricKey.Algo)
}
// GetBase64Key returns the session key as base64 encoded string.
func (symmetricKey *SymmetricKey) GetBase64Key() string {
return base64.StdEncoding.EncodeToString(symmetricKey.Key)
}
func NewSymmetricKeyFromToken(passphrase, algo string) *SymmetricKey {
return NewSymmetricKey([]byte(passphrase), algo)
}
func NewSymmetricKey(key []byte, algo string) *SymmetricKey {
return &SymmetricKey{
Key: key,
Algo: algo,
}
}
func newSymmetricKeyFromEncrypted(ek *packet.EncryptedKey) (*SymmetricKey, error) {
var algo string
for k, v := range symKeyAlgos {
if v == ek.CipherFunc {
algo = k
break
}
}
if algo == "" {
return nil, fmt.Errorf("gopenpgp: unsupported cipher function: %v", ek.CipherFunc)
}
return NewSymmetricKey(ek.Key, algo), nil
}
// Encrypt encrypts a PlainMessage to PGPMessage with a SymmetricKey
// message : The plain data as a PlainMessage
// output : The encrypted data as PGPMessage
func (symmetricKey *SymmetricKey) Encrypt(message *PlainMessage) (*PGPMessage, error) {
encrypted, err := symmetricEncrypt(message.GetBinary(), symmetricKey)
if err != nil {
return nil, err
}
return NewPGPMessage(encrypted), nil
}
// Decrypt decrypts password protected pgp binary messages
// encrypted: PGPMessage
// output: PlainMessage
func (symmetricKey *SymmetricKey) Decrypt(message *PGPMessage) (*PlainMessage, error) {
decrypted, err := symmetricDecrypt(message.NewReader(), symmetricKey)
if err != nil {
return nil, err
}
binMessage := NewPlainMessage(decrypted)
return binMessage, nil
}
// NewSymmetricKeyFromKeyPacket decrypts the binary symmetrically encrypted
// session key packet and returns the session key.
func NewSymmetricKeyFromKeyPacket(keyPacket []byte, password string) (*SymmetricKey, error) {
keyReader := bytes.NewReader(keyPacket)
packets := packet.NewReader(keyReader)
var symKeys []*packet.SymmetricKeyEncrypted
for {
var p packet.Packet
var err error
if p, err = packets.Next(); err != nil {
break
}
switch p := p.(type) {
case *packet.SymmetricKeyEncrypted:
symKeys = append(symKeys, p)
}
}
pwdRaw := []byte(password)
// Try the symmetric passphrase first
if len(symKeys) != 0 && pwdRaw != nil {
for _, s := range symKeys {
key, cipherFunc, err := s.Decrypt(pwdRaw)
if err == nil {
return &SymmetricKey{
Key: key,
Algo: getAlgo(cipherFunc),
}, nil
}
}
}
return nil, errors.New("gopenpgp: password incorrect")
}
// EncryptToKeyPacket encrypts the session key with the password and
// returns a binary symmetrically encrypted session key packet.
func (symmetricKey *SymmetricKey) EncryptToKeyPacket(password string) ([]byte, error) {
outbuf := &bytes.Buffer{}
cf := symmetricKey.GetCipherFunc()
if len(password) <= 0 {
return nil, errors.New("gopenpgp: password can't be empty")
}
pwdRaw := []byte(password)
config := &packet.Config{
DefaultCipher: cf,
}
err := packet.SerializeSymmetricKeyEncryptedReuseKey(outbuf, symmetricKey.Key, pwdRaw, config)
if err != nil {
return nil, err
}
return outbuf.Bytes(), nil
}
// ----- INTERNAL FUNCTIONS ------
func symmetricEncrypt(message []byte, sk *SymmetricKey) ([]byte, error) {
var outBuf bytes.Buffer
config := &packet.Config{
Time: pgp.getTimeGenerator(),
DefaultCipher: sk.GetCipherFunc(),
}
encryptWriter, err := openpgp.SymmetricallyEncrypt(&outBuf, sk.Key, nil, config)
if err != nil {
return nil, err
}
_, err = encryptWriter.Write(message)
encryptWriter.Close()
if err != nil {
return nil, err
}
return outBuf.Bytes(), nil
}
func symmetricDecrypt(encryptedIO io.Reader, sk *SymmetricKey) ([]byte, error) {
firstTimeCalled := true
var prompt = func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
if firstTimeCalled {
firstTimeCalled = false
return sk.Key, nil
}
return nil, errors.New("gopenpgp: wrong password in symmetric decryption")
}
config := &packet.Config{
Time: pgp.getTimeGenerator(),
}
md, err := openpgp.ReadMessage(encryptedIO, nil, prompt, config)
if err != nil {
return nil, err
}
messageBuf := bytes.NewBuffer(nil)
_, err = io.Copy(messageBuf, md.UnverifiedBody)
if err != nil {
return nil, err
}
return messageBuf.Bytes(), nil
}
func getAlgo(cipher packet.CipherFunction) string {
algo := constants.AES256
for k, v := range symKeyAlgos {
if v == cipher {
algo = k
break
}
}
return algo
}