github.com/zmap/zcrypto@v0.0.0-20240512203510-0fef58d9a9db/x509/pem_decrypt.go (about)

     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  	"strings"
    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 := make([]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 if the PEM block is password encrypted.
    99  func IsEncryptedPEMBlock(b *pem.Block) bool {
   100  	_, ok := b.Headers["DEK-Info"]
   101  	return ok
   102  }
   103  
   104  // IncorrectPasswordError is returned when an incorrect password is detected.
   105  var IncorrectPasswordError = errors.New("x509: decryption password incorrect")
   106  
   107  // DecryptPEMBlock takes a password encrypted PEM block and the password used to
   108  // encrypt it and returns a slice of decrypted DER encoded bytes. It inspects
   109  // the DEK-Info header to determine the algorithm used for decryption. If no
   110  // DEK-Info header is present, an error is returned. If an incorrect password
   111  // is detected an IncorrectPasswordError is returned. Because of deficiencies
   112  // in the encrypted-PEM format, it's not always possible to detect an incorrect
   113  // password. In these cases no error will be returned but the decrypted DER
   114  // bytes will be random noise.
   115  func DecryptPEMBlock(b *pem.Block, password []byte) ([]byte, error) {
   116  	dek, ok := b.Headers["DEK-Info"]
   117  	if !ok {
   118  		return nil, errors.New("x509: no DEK-Info header in block")
   119  	}
   120  
   121  	idx := strings.Index(dek, ",")
   122  	if idx == -1 {
   123  		return nil, errors.New("x509: malformed DEK-Info header")
   124  	}
   125  
   126  	mode, hexIV := dek[:idx], dek[idx+1:]
   127  	ciph := cipherByName(mode)
   128  	if ciph == nil {
   129  		return nil, errors.New("x509: unknown encryption mode")
   130  	}
   131  	iv, err := hex.DecodeString(hexIV)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	if len(iv) != ciph.blockSize {
   136  		return nil, errors.New("x509: incorrect IV size")
   137  	}
   138  
   139  	// Based on the OpenSSL implementation. The salt is the first 8 bytes
   140  	// of the initialization vector.
   141  	key := ciph.deriveKey(password, iv[:8])
   142  	block, err := ciph.cipherFunc(key)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	if len(b.Bytes)%block.BlockSize() != 0 {
   148  		return nil, errors.New("x509: encrypted PEM data is not a multiple of the block size")
   149  	}
   150  
   151  	data := make([]byte, len(b.Bytes))
   152  	dec := cipher.NewCBCDecrypter(block, iv)
   153  	dec.CryptBlocks(data, b.Bytes)
   154  
   155  	// Blocks are padded using a scheme where the last n bytes of padding are all
   156  	// equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423.
   157  	// For example:
   158  	//	[x y z 2 2]
   159  	//	[x y 7 7 7 7 7 7 7]
   160  	// If we detect a bad padding, we assume it is an invalid password.
   161  	dlen := len(data)
   162  	if dlen == 0 || dlen%ciph.blockSize != 0 {
   163  		return nil, errors.New("x509: invalid padding")
   164  	}
   165  	last := int(data[dlen-1])
   166  	if dlen < last {
   167  		return nil, IncorrectPasswordError
   168  	}
   169  	if last == 0 || last > ciph.blockSize {
   170  		return nil, IncorrectPasswordError
   171  	}
   172  	for _, val := range data[dlen-last:] {
   173  		if int(val) != last {
   174  			return nil, IncorrectPasswordError
   175  		}
   176  	}
   177  	return data[:dlen-last], nil
   178  }
   179  
   180  // EncryptPEMBlock returns a PEM block of the specified type holding the
   181  // given DER-encoded data encrypted with the specified algorithm and
   182  // password.
   183  func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
   184  	ciph := cipherByKey(alg)
   185  	if ciph == nil {
   186  		return nil, errors.New("x509: unknown encryption mode")
   187  	}
   188  	iv := make([]byte, ciph.blockSize)
   189  	if _, err := io.ReadFull(rand, iv); err != nil {
   190  		return nil, errors.New("x509: cannot generate IV: " + err.Error())
   191  	}
   192  	// The salt is the first 8 bytes of the initialization vector,
   193  	// matching the key derivation in DecryptPEMBlock.
   194  	key := ciph.deriveKey(password, iv[:8])
   195  	block, err := ciph.cipherFunc(key)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	enc := cipher.NewCBCEncrypter(block, iv)
   200  	pad := ciph.blockSize - len(data)%ciph.blockSize
   201  	encrypted := make([]byte, len(data), len(data)+pad)
   202  	// We could save this copy by encrypting all the whole blocks in
   203  	// the data separately, but it doesn't seem worth the additional
   204  	// code.
   205  	copy(encrypted, data)
   206  	// See RFC 1423, section 1.1
   207  	for i := 0; i < pad; i++ {
   208  		encrypted = append(encrypted, byte(pad))
   209  	}
   210  	enc.CryptBlocks(encrypted, encrypted)
   211  
   212  	return &pem.Block{
   213  		Type: blockType,
   214  		Headers: map[string]string{
   215  			"Proc-Type": "4,ENCRYPTED",
   216  			"DEK-Info":  ciph.name + "," + hex.EncodeToString(iv),
   217  		},
   218  		Bytes: encrypted,
   219  	}, nil
   220  }
   221  
   222  func cipherByName(name string) *rfc1423Algo {
   223  	for i := range rfc1423Algos {
   224  		alg := &rfc1423Algos[i]
   225  		if alg.name == name {
   226  			return alg
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  func cipherByKey(key PEMCipher) *rfc1423Algo {
   233  	for i := range rfc1423Algos {
   234  		alg := &rfc1423Algos[i]
   235  		if alg.cipher == key {
   236  			return alg
   237  		}
   238  	}
   239  	return nil
   240  }