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