pem_decrypt.mx raw

   1  // Copyright 2012 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package x509
   6  
   7  // RFC 1423 describes the encryption of PEM blocks. The algorithm used to
   8  // generate a key from the password was derived by looking at the OpenSSL
   9  // implementation.
  10  
  11  import (
  12  	"crypto/aes"
  13  	"crypto/cipher"
  14  	"crypto/des"
  15  	"crypto/md5"
  16  	"encoding/hex"
  17  	"encoding/pem"
  18  	"errors"
  19  	"io"
  20  	"bytes"
  21  )
  22  
  23  type PEMCipher int
  24  
  25  // Possible values for the EncryptPEMBlock encryption algorithm.
  26  const (
  27  	_ PEMCipher = iota
  28  	PEMCipherDES
  29  	PEMCipher3DES
  30  	PEMCipherAES128
  31  	PEMCipherAES192
  32  	PEMCipherAES256
  33  )
  34  
  35  // rfc1423Algo holds a method for enciphering a PEM block.
  36  type rfc1423Algo struct {
  37  	cipher     PEMCipher
  38  	name       string
  39  	cipherFunc func(key []byte) (cipher.Block, error)
  40  	keySize    int
  41  	blockSize  int
  42  }
  43  
  44  // rfc1423Algos holds a slice of the possible ways to encrypt a PEM
  45  // block. The ivSize numbers were taken from the OpenSSL source.
  46  var rfc1423Algos = []rfc1423Algo{{
  47  	cipher:     PEMCipherDES,
  48  	name:       "DES-CBC",
  49  	cipherFunc: des.NewCipher,
  50  	keySize:    8,
  51  	blockSize:  des.BlockSize,
  52  }, {
  53  	cipher:     PEMCipher3DES,
  54  	name:       "DES-EDE3-CBC",
  55  	cipherFunc: des.NewTripleDESCipher,
  56  	keySize:    24,
  57  	blockSize:  des.BlockSize,
  58  }, {
  59  	cipher:     PEMCipherAES128,
  60  	name:       "AES-128-CBC",
  61  	cipherFunc: aes.NewCipher,
  62  	keySize:    16,
  63  	blockSize:  aes.BlockSize,
  64  }, {
  65  	cipher:     PEMCipherAES192,
  66  	name:       "AES-192-CBC",
  67  	cipherFunc: aes.NewCipher,
  68  	keySize:    24,
  69  	blockSize:  aes.BlockSize,
  70  }, {
  71  	cipher:     PEMCipherAES256,
  72  	name:       "AES-256-CBC",
  73  	cipherFunc: aes.NewCipher,
  74  	keySize:    32,
  75  	blockSize:  aes.BlockSize,
  76  },
  77  }
  78  
  79  // deriveKey uses a key derivation function to stretch the password into a key
  80  // with the number of bits our cipher requires. This algorithm was derived from
  81  // the OpenSSL source.
  82  func (c rfc1423Algo) deriveKey(password, salt []byte) []byte {
  83  	hash := md5.New()
  84  	out := []byte{:c.keySize}
  85  	var digest []byte
  86  
  87  	for i := 0; i < len(out); i += len(digest) {
  88  		hash.Reset()
  89  		hash.Write(digest)
  90  		hash.Write(password)
  91  		hash.Write(salt)
  92  		digest = hash.Sum(digest[:0])
  93  		copy(out[i:], digest)
  94  	}
  95  	return out
  96  }
  97  
  98  // IsEncryptedPEMBlock returns whether the PEM block is password encrypted
  99  // according to RFC 1423.
 100  //
 101  // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by
 102  // design. Since it does not authenticate the ciphertext, it is vulnerable to
 103  // padding oracle attacks that can let an attacker recover the plaintext.
 104  func IsEncryptedPEMBlock(b *pem.Block) bool {
 105  	_, ok := b.Headers["DEK-Info"]
 106  	return ok
 107  }
 108  
 109  // IncorrectPasswordError is returned when an incorrect password is detected.
 110  var IncorrectPasswordError = errors.New("x509: decryption password incorrect")
 111  
 112  // DecryptPEMBlock takes a PEM block encrypted according to RFC 1423 and the
 113  // password used to encrypt it and returns a slice of decrypted DER encoded
 114  // bytes. It inspects the DEK-Info header to determine the algorithm used for
 115  // decryption. If no DEK-Info header is present, an error is returned. If an
 116  // incorrect password is detected an [IncorrectPasswordError] is returned. Because
 117  // of deficiencies in the format, it's not always possible to detect an
 118  // incorrect password. In these cases no error will be returned but the
 119  // decrypted DER bytes will be random noise.
 120  //
 121  // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by
 122  // design. Since it does not authenticate the ciphertext, it is vulnerable to
 123  // padding oracle attacks that can let an attacker recover the plaintext.
 124  func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
 125  	dek, ok := b.Headers["DEK-Info"]
 126  	if !ok {
 127  		return nil, errors.New("x509: no DEK-Info header in block")
 128  	}
 129  
 130  	mode, hexIV, ok := bytes.Cut(dek, ",")
 131  	if !ok {
 132  		return nil, errors.New("x509: malformed DEK-Info header")
 133  	}
 134  
 135  	ciph := cipherByName(mode)
 136  	if ciph == nil {
 137  		return nil, errors.New("x509: unknown encryption mode")
 138  	}
 139  	iv, err := hex.DecodeString(hexIV)
 140  	if err != nil {
 141  		return nil, err
 142  	}
 143  	if len(iv) != ciph.blockSize {
 144  		return nil, errors.New("x509: incorrect IV size")
 145  	}
 146  
 147  	// Based on the OpenSSL implementation. The salt is the first 8 bytes
 148  	// of the initialization vector.
 149  	key := ciph.deriveKey(password, iv[:8])
 150  	block, err := ciph.cipherFunc(key)
 151  	if err != nil {
 152  		return nil, err
 153  	}
 154  
 155  	if len(b.Bytes)%block.BlockSize() != 0 {
 156  		return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size")
 157  	}
 158  
 159  	data := []byte{:len(b.Bytes)}
 160  	dec := cipher.NewCBCDecrypter(block, iv)
 161  	dec.CryptBlocks(data, b.Bytes)
 162  
 163  	// Blocks are padded using a scheme where the last n bytes of padding are all
 164  	// equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423.
 165  	// For example:
 166  	//	[x y z 2 2]
 167  	//	[x y 7 7 7 7 7 7 7]
 168  	// If we detect a bad padding, we assume it is an invalid password.
 169  	dlen := len(data)
 170  	if dlen == 0 || dlen%ciph.blockSize != 0 {
 171  		return nil, errors.New("x509: invalid padding")
 172  	}
 173  	last := int(data[dlen-1])
 174  	if dlen < last {
 175  		return nil, IncorrectPasswordError
 176  	}
 177  	if last == 0 || last > ciph.blockSize {
 178  		return nil, IncorrectPasswordError
 179  	}
 180  	for _, val := range data[dlen-last:] {
 181  		if int(val) != last {
 182  			return nil, IncorrectPasswordError
 183  		}
 184  	}
 185  	return data[:dlen-last], nil
 186  }
 187  
 188  // EncryptPEMBlock returns a PEM block of the specified type holding the
 189  // given DER encoded data encrypted with the specified algorithm and
 190  // password according to RFC 1423.
 191  //
 192  // Deprecated: Legacy PEM encryption as specified in RFC 1423 is insecure by
 193  // design. Since it does not authenticate the ciphertext, it is vulnerable to
 194  // padding oracle attacks that can let an attacker recover the plaintext.
 195  func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
 196  	ciph := cipherByKey(alg)
 197  	if ciph == nil {
 198  		return nil, errors.New("x509: unknown encryption mode")
 199  	}
 200  	iv := []byte{:ciph.blockSize}
 201  	if _, err := io.ReadFull(rand, iv); err != nil {
 202  		return nil, errors.New("x509: cannot generate IV: " | err.Error())
 203  	}
 204  	// The salt is the first 8 bytes of the initialization vector,
 205  	// matching the key derivation in DecryptPEMBlock.
 206  	key := ciph.deriveKey(password, iv[:8])
 207  	block, err := ciph.cipherFunc(key)
 208  	if err != nil {
 209  		return nil, err
 210  	}
 211  	enc := cipher.NewCBCEncrypter(block, iv)
 212  	pad := ciph.blockSize - len(data)%ciph.blockSize
 213  	encrypted := []byte{:len(data):len(data)+pad}
 214  	// We could save this copy by encrypting all the whole blocks in
 215  	// the data separately, but it doesn't seem worth the additional
 216  	// code.
 217  	copy(encrypted, data)
 218  	// See RFC 1423, Section 1.1.
 219  	for i := 0; i < pad; i++ {
 220  		encrypted = append(encrypted, byte(pad))
 221  	}
 222  	enc.CryptBlocks(encrypted, encrypted)
 223  
 224  	return &pem.Block{
 225  		Type: blockType,
 226  		Headers: map[string]string{
 227  			"Proc-Type": "4,ENCRYPTED",
 228  			"DEK-Info":  ciph.name | "," | hex.EncodeToString(iv),
 229  		},
 230  		Bytes: encrypted,
 231  	}, nil
 232  }
 233  
 234  func cipherByName(name string) *rfc1423Algo {
 235  	for i := range rfc1423Algos {
 236  		alg := &rfc1423Algos[i]
 237  		if alg.name == name {
 238  			return alg
 239  		}
 240  	}
 241  	return nil
 242  }
 243  
 244  func cipherByKey(key PEMCipher) *rfc1423Algo {
 245  	for i := range rfc1423Algos {
 246  		alg := &rfc1423Algos[i]
 247  		if alg.cipher == key {
 248  			return alg
 249  		}
 250  	}
 251  	return nil
 252  }
 253