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 }