github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/crypto/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  	data := make([]byte, len(b.Bytes))
   148  	dec := cipher.NewCBCDecrypter(block, iv)
   149  	dec.CryptBlocks(data, b.Bytes)
   150  
   151  	// Blocks are padded using a scheme where the last n bytes of padding are all
   152  	// equal to n. It can pad from 1 to blocksize bytes inclusive. See RFC 1423.
   153  	// For example:
   154  	//	[x y z 2 2]
   155  	//	[x y 7 7 7 7 7 7 7]
   156  	// If we detect a bad padding, we assume it is an invalid password.
   157  	dlen := len(data)
   158  	if dlen == 0 || dlen%ciph.blockSize != 0 {
   159  		return nil, errors.New("x509: invalid padding")
   160  	}
   161  	last := int(data[dlen-1])
   162  	if dlen < last {
   163  		return nil, IncorrectPasswordError
   164  	}
   165  	if last == 0 || last > ciph.blockSize {
   166  		return nil, IncorrectPasswordError
   167  	}
   168  	for _, val := range data[dlen-last:] {
   169  		if int(val) != last {
   170  			return nil, IncorrectPasswordError
   171  		}
   172  	}
   173  	return data[:dlen-last], nil
   174  }
   175  
   176  // EncryptPEMBlock returns a PEM block of the specified type holding the
   177  // given DER-encoded data encrypted with the specified algorithm and
   178  // password.
   179  func EncryptPEMBlock(rand io.Reader, blockType string, data, password []byte, alg PEMCipher) (*pem.Block, error) {
   180  	ciph := cipherByKey(alg)
   181  	if ciph == nil {
   182  		return nil, errors.New("x509: unknown encryption mode")
   183  	}
   184  	iv := make([]byte, ciph.blockSize)
   185  	if _, err := io.ReadFull(rand, iv); err != nil {
   186  		return nil, errors.New("x509: cannot generate IV: " + err.Error())
   187  	}
   188  	// The salt is the first 8 bytes of the initialization vector,
   189  	// matching the key derivation in DecryptPEMBlock.
   190  	key := ciph.deriveKey(password, iv[:8])
   191  	block, err := ciph.cipherFunc(key)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	enc := cipher.NewCBCEncrypter(block, iv)
   196  	pad := ciph.blockSize - len(data)%ciph.blockSize
   197  	encrypted := make([]byte, len(data), len(data)+pad)
   198  	// We could save this copy by encrypting all the whole blocks in
   199  	// the data separately, but it doesn't seem worth the additional
   200  	// code.
   201  	copy(encrypted, data)
   202  	// See RFC 1423, section 1.1
   203  	for i := 0; i < pad; i++ {
   204  		encrypted = append(encrypted, byte(pad))
   205  	}
   206  	enc.CryptBlocks(encrypted, encrypted)
   207  
   208  	return &pem.Block{
   209  		Type: blockType,
   210  		Headers: map[string]string{
   211  			"Proc-Type": "4,ENCRYPTED",
   212  			"DEK-Info":  ciph.name + "," + hex.EncodeToString(iv),
   213  		},
   214  		Bytes: encrypted,
   215  	}, nil
   216  }
   217  
   218  func cipherByName(name string) *rfc1423Algo {
   219  	for i := range rfc1423Algos {
   220  		alg := &rfc1423Algos[i]
   221  		if alg.name == name {
   222  			return alg
   223  		}
   224  	}
   225  	return nil
   226  }
   227  
   228  func cipherByKey(key PEMCipher) *rfc1423Algo {
   229  	for i := range rfc1423Algos {
   230  		alg := &rfc1423Algos[i]
   231  		if alg.cipher == key {
   232  			return alg
   233  		}
   234  	}
   235  	return nil
   236  }