diff options
author | xuri <xuri.me@gmail.com> | 2020-09-02 23:14:19 +0800 |
---|---|---|
committer | xuri <xuri.me@gmail.com> | 2020-09-02 23:14:19 +0800 |
commit | 98f1a699033b76a1482edc03d533dd1f67bcd2d6 (patch) | |
tree | 9cacf35fadf2a67863e4d7801f041c2933e375a3 /crypt.go | |
parent | 4177c1585e312bee00c1592af3df6423c366e806 (diff) |
support ECMA-376 document standard encryption, ref #199
Diffstat (limited to 'crypt.go')
-rw-r--r-- | crypt.go | 481 |
1 files changed, 481 insertions, 0 deletions
diff --git a/crypt.go b/crypt.go new file mode 100644 index 0000000..f8dd597 --- /dev/null +++ b/crypt.go @@ -0,0 +1,481 @@ +// Copyright 2016 - 2020 The excelize Authors. All rights reserved. Use of +// this source code is governed by a BSD-style license that can be found in +// the LICENSE file. +// +// Package excelize providing a set of functions that allow you to write to +// and read from XLSX files. Support reads and writes XLSX file generated by +// Microsoft Excelâ„¢ 2007 and later. Support save file without losing original +// charts of XLSX. This library needs Go version 1.10 or later. + +package excelize + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/binary" + "encoding/xml" + "errors" + "hash" + "strings" + + "github.com/richardlehane/mscfb" + "golang.org/x/crypto/md4" + "golang.org/x/crypto/ripemd160" + "golang.org/x/text/encoding/unicode" +) + +var ( + blockKey = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption + packageOffset = 8 // First 8 bytes are the size of the stream + packageEncryptionChunkSize = 4096 + iterCount = 50000 + cryptoIdentifier = []byte{ // checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier + 0x3c, 0x00, 0x00, 0x00, 0x4d, 0x00, 0x69, 0x00, 0x63, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x73, 0x00, + 0x6f, 0x00, 0x66, 0x00, 0x74, 0x00, 0x2e, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x61, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x72, 0x00, 0x2e, 0x00, 0x44, 0x00, 0x61, 0x00, + 0x74, 0x00, 0x61, 0x00, 0x53, 0x00, 0x70, 0x00, 0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x73, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + } + oleIdentifier = []byte{ + 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1, + } +) + +// Encryption specifies the encryption structure, streams, and storages are +// required when encrypting ECMA-376 documents. +type Encryption struct { + KeyData KeyData `xml:"keyData"` + DataIntegrity DataIntegrity `xml:"dataIntegrity"` + KeyEncryptors KeyEncryptors `xml:"keyEncryptors"` +} + +// KeyData specifies the cryptographic attributes used to encrypt the data. +type KeyData struct { + SaltSize int `xml:"saltSize,attr"` + BlockSize int `xml:"blockSize,attr"` + KeyBits int `xml:"keyBits,attr"` + HashSize int `xml:"hashSize,attr"` + CipherAlgorithm string `xml:"cipherAlgorithm,attr"` + CipherChaining string `xml:"cipherChaining,attr"` + HashAlgorithm string `xml:"hashAlgorithm,attr"` + SaltValue string `xml:"saltValue,attr"` +} + +// DataIntegrity specifies the encrypted copies of the salt and hash values +// used to help ensure that the integrity of the encrypted data has not been +// compromised. +type DataIntegrity struct { + EncryptedHmacKey string `xml:"encryptedHmacKey,attr"` + EncryptedHmacValue string `xml:"encryptedHmacValue,attr"` +} + +// KeyEncryptors specifies the key encryptors used to encrypt the data. +type KeyEncryptors struct { + KeyEncryptor []KeyEncryptor `xml:"keyEncryptor"` +} + +// KeyEncryptor specifies that the schema used by this encryptor is the schema +// specified for password-based encryptors. +type KeyEncryptor struct { + XMLName xml.Name `xml:"keyEncryptor"` + URI string `xml:"uri,attr"` + EncryptedKey EncryptedKey `xml:"encryptedKey"` +} + +// EncryptedKey used to generate the encrypting key. +type EncryptedKey struct { + XMLName xml.Name `xml:"http://schemas.microsoft.com/office/2006/keyEncryptor/password encryptedKey"` + SpinCount int `xml:"spinCount,attr"` + EncryptedVerifierHashInput string `xml:"encryptedVerifierHashInput,attr"` + EncryptedVerifierHashValue string `xml:"encryptedVerifierHashValue,attr"` + EncryptedKeyValue string `xml:"encryptedKeyValue,attr"` + KeyData +} + +// StandardEncryptionHeader structure is used by ECMA-376 document encryption +// [ECMA-376] and Office binary document RC4 CryptoAPI encryption, to specify +// encryption properties for an encrypted stream. +type StandardEncryptionHeader struct { + Flags uint32 + SizeExtra uint32 + AlgID uint32 + AlgIDHash uint32 + KeySize uint32 + ProviderType uint32 + Reserved1 uint32 + Reserved2 uint32 + CspName string +} + +// StandardEncryptionVerifier structure is used by Office Binary Document RC4 +// CryptoAPI Encryption and ECMA-376 Document Encryption. Every usage of this +// structure MUST specify the hashing algorithm and encryption algorithm used +// in the EncryptionVerifier structure. +type StandardEncryptionVerifier struct { + SaltSize uint32 + Salt []byte + EncryptedVerifier []byte + VerifierHashSize uint32 + EncryptedVerifierHash []byte +} + +// Decrypt API decrypt the CFB file format with ECMA-376 agile encryption and +// standard encryption. Support cryptographic algorithm: MD4, MD5, RIPEMD-160, +// SHA1, SHA256, SHA384 and SHA512 currently. +func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) { + doc, err := mscfb.New(bytes.NewReader(raw)) + if err != nil { + return + } + encryptionInfoBuf, encryptedPackageBuf := extractPart(doc) + mechanism, err := encryptionMechanism(encryptionInfoBuf) + if err != nil || mechanism == "extensible" { + return + } + switch mechanism { + case "agile": + return agileDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) + case "standard": + return standardDecrypt(encryptionInfoBuf, encryptedPackageBuf, opt) + default: + err = errors.New("unsupport encryption mechanism") + break + } + return +} + +// extractPart extract data from storage by specified part name. +func extractPart(doc *mscfb.Reader) (encryptionInfoBuf, encryptedPackageBuf []byte) { + for entry, err := doc.Next(); err == nil; entry, err = doc.Next() { + switch entry.Name { + case "EncryptionInfo": + buf := make([]byte, entry.Size) + i, _ := doc.Read(buf) + if i > 0 { + encryptionInfoBuf = buf + break + } + case "EncryptedPackage": + buf := make([]byte, entry.Size) + i, _ := doc.Read(buf) + if i > 0 { + encryptedPackageBuf = buf + break + } + } + } + return +} + +// encryptionMechanism parse password-protected documents created mechanism. +func encryptionMechanism(buffer []byte) (mechanism string, err error) { + if len(buffer) < 4 { + err = errors.New("unknown encryption mechanism") + return + } + versionMajor, versionMinor := binary.LittleEndian.Uint16(buffer[0:2]), binary.LittleEndian.Uint16(buffer[2:4]) + if versionMajor == 4 && versionMinor == 4 { + mechanism = "agile" + return + } else if (2 <= versionMajor && versionMajor <= 4) && versionMinor == 2 { + mechanism = "standard" + return + } else if (versionMajor == 3 || versionMajor == 4) && versionMinor == 3 { + mechanism = "extensible" + } + err = errors.New("unsupport encryption mechanism") + return +} + +// ECMA-376 Standard Encryption + +// standardDecrypt decrypt the CFB file format with ECMA-376 standard encryption. +func standardDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) ([]byte, error) { + encryptionHeaderSize := binary.LittleEndian.Uint32(encryptionInfoBuf[8:12]) + block := encryptionInfoBuf[12 : 12+encryptionHeaderSize] + header := StandardEncryptionHeader{ + Flags: binary.LittleEndian.Uint32(block[:4]), + SizeExtra: binary.LittleEndian.Uint32(block[4:8]), + AlgID: binary.LittleEndian.Uint32(block[8:12]), + AlgIDHash: binary.LittleEndian.Uint32(block[12:16]), + KeySize: binary.LittleEndian.Uint32(block[16:20]), + ProviderType: binary.LittleEndian.Uint32(block[20:24]), + Reserved1: binary.LittleEndian.Uint32(block[24:28]), + Reserved2: binary.LittleEndian.Uint32(block[28:32]), + CspName: string(block[32:]), + } + block = encryptionInfoBuf[12+encryptionHeaderSize:] + algIDMap := map[uint32]string{ + 0x0000660E: "AES-128", + 0x0000660F: "AES-192", + 0x00006610: "AES-256", + } + algorithm := "AES" + _, ok := algIDMap[header.AlgID] + if !ok { + algorithm = "RC4" + } + verifier := standardEncryptionVerifier(algorithm, block) + secretKey, err := standardConvertPasswdToKey(header, verifier, opt) + if err != nil { + return nil, err + } + // decrypted data + x := encryptedPackageBuf[8:] + blob, err := aes.NewCipher(secretKey) + if err != nil { + return nil, err + } + decrypted := make([]byte, len(x)) + size := 16 + for bs, be := 0, size; bs < len(x); bs, be = bs+size, be+size { + blob.Decrypt(decrypted[bs:be], x[bs:be]) + } + return decrypted, err +} + +// standardEncryptionVerifier extract ECMA-376 standard encryption verifier. +func standardEncryptionVerifier(algorithm string, blob []byte) StandardEncryptionVerifier { + verifier := StandardEncryptionVerifier{ + SaltSize: binary.LittleEndian.Uint32(blob[:4]), + Salt: blob[4:20], + EncryptedVerifier: blob[20:36], + VerifierHashSize: binary.LittleEndian.Uint32(blob[36:40]), + } + if algorithm == "RC4" { + verifier.EncryptedVerifierHash = blob[40:60] + } else if algorithm == "AES" { + verifier.EncryptedVerifierHash = blob[40:72] + } + return verifier +} + +// standardConvertPasswdToKey generate intermediate key from given password. +func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier StandardEncryptionVerifier, opt *Options) ([]byte, error) { + encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() + passwordBuffer, err := encoder.Bytes([]byte(opt.Password)) + if err != nil { + return nil, err + } + key := hashing("sha1", verifier.Salt, passwordBuffer) + for i := 0; i < iterCount; i++ { + iterator := createUInt32LEBuffer(i) + key = hashing("sha1", iterator, key) + } + var block int + hfinal := hashing("sha1", key, createUInt32LEBuffer(block)) + cbRequiredKeyLength := int(header.KeySize) / 8 + cbHash := sha1.Size + buf1 := bytes.Repeat([]byte{0x36}, 64) + buf1 = append(standardXORBytes(hfinal, buf1[:cbHash]), buf1[cbHash:]...) + x1 := hashing("sha1", buf1) + buf2 := bytes.Repeat([]byte{0x5c}, 64) + buf2 = append(standardXORBytes(hfinal, buf2[:cbHash]), buf2[cbHash:]...) + x2 := hashing("sha1", buf2) + x3 := append(x1, x2...) + keyDerived := x3[:cbRequiredKeyLength] + return keyDerived, err +} + +// standardXORBytes perform XOR operations for two bytes slice. +func standardXORBytes(a, b []byte) []byte { + r := make([][2]byte, len(a), len(a)) + for i, e := range a { + r[i] = [2]byte{e, b[i]} + } + buf := make([]byte, len(a)) + for p, q := range r { + buf[p] = q[0] ^ q[1] + } + return buf +} + +// ECMA-376 Agile Encryption + +// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption. +// Support cryptographic algorithm: MD4, MD5, RIPEMD-160, SHA1, SHA256, SHA384 +// and SHA512. +func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (packageBuf []byte, err error) { + var encryptionInfo Encryption + if encryptionInfo, err = parseEncryptionInfo(encryptionInfoBuf[8:]); err != nil { + return + } + // Convert the password into an encryption key. + key, err := convertPasswdToKey(opt.Password, encryptionInfo) + if err != nil { + return + } + // Use the key to decrypt the package key. + encryptedKey := encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey + saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) + if err != nil { + return + } + encryptedKeyValue, err := base64.StdEncoding.DecodeString(encryptedKey.EncryptedKeyValue) + if err != nil { + return + } + packageKey, err := crypt(false, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, key, saltValue, encryptedKeyValue) + // Use the package key to decrypt the package. + return cryptPackage(false, packageKey, encryptedPackageBuf, encryptionInfo) +} + +// convertPasswdToKey convert the password into an encryption key. +func convertPasswdToKey(passwd string, encryption Encryption) (key []byte, err error) { + var b bytes.Buffer + saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue) + if err != nil { + return + } + b.Write(saltValue) + encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() + passwordBuffer, err := encoder.Bytes([]byte(passwd)) + if err != nil { + return + } + b.Write(passwordBuffer) + // Generate the initial hash. + key = hashing(encryption.KeyData.HashAlgorithm, b.Bytes()) + // Now regenerate until spin count. + for i := 0; i < encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SpinCount; i++ { + iterator := createUInt32LEBuffer(i) + key = hashing(encryption.KeyData.HashAlgorithm, iterator, key) + } + // Now generate the final hash. + key = hashing(encryption.KeyData.HashAlgorithm, key, blockKey) + // Truncate or pad as needed to get to length of keyBits. + keyBytes := encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.KeyBits / 8 + if len(key) < keyBytes { + tmp := make([]byte, 0x36) + key = append(key, tmp...) + key = tmp + } else if len(key) > keyBytes { + key = key[:keyBytes] + } + return +} + +// hashing data by specified hash algorithm. +func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) { + var hashMap = map[string]hash.Hash{ + "md4": md4.New(), + "md5": md5.New(), + "ripemd-160": ripemd160.New(), + "sha1": sha1.New(), + "sha256": sha256.New(), + "sha384": sha512.New384(), + "sha512": sha512.New(), + } + handler, ok := hashMap[strings.ToLower(hashAlgorithm)] + if !ok { + return key + } + for _, buf := range buffer { + handler.Write(buf) + } + key = handler.Sum(nil) + return key +} + +// createUInt32LEBuffer create buffer with little endian 32-bit unsigned +// integer. +func createUInt32LEBuffer(value int) []byte { + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(value)) + return buf +} + +// parseEncryptionInfo parse the encryption info XML into an object. +func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err error) { + err = xml.Unmarshal(encryptionInfo, &encryption) + return +} + +// crypt encrypt / decrypt input by given cipher algorithm, cipher chaining, +// key and initialization vector. +func crypt(encrypt bool, cipherAlgorithm, cipherChaining string, key, iv, input []byte) (packageKey []byte, err error) { + block, err := aes.NewCipher(key) + if err != nil { + return input, err + } + stream := cipher.NewCBCDecrypter(block, iv) + stream.CryptBlocks(input, input) + return input, nil +} + +// cryptPackage encrypt / decrypt package by given packageKey and encryption +// info. +func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) { + encryptedKey := encryption.KeyData + var offset = packageOffset + if encrypt { + offset = 0 + } + var i, start, end int + var iv, outputChunk []byte + for end < len(input) { + start = end + end = start + packageEncryptionChunkSize + + if end > len(input) { + end = len(input) + } + // Grab the next chunk + var inputChunk []byte + if (end + offset) < len(input) { + inputChunk = input[start+offset : end+offset] + } else { + inputChunk = input[start+offset : end] + } + + // Pad the chunk if it is not an integer multiple of the block size + remainder := len(inputChunk) % encryptedKey.BlockSize + if remainder != 0 { + inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...) + } + // Create the initialization vector + iv, err = createIV(encrypt, i, encryption) + if err != nil { + return + } + // Encrypt/decrypt the chunk and add it to the array + outputChunk, err = crypt(encrypt, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, packageKey, iv, inputChunk) + if err != nil { + return + } + outputChunks = append(outputChunks, outputChunk...) + i++ + } + return +} + +// createIV create an initialization vector (IV). +func createIV(encrypt bool, blockKey int, encryption Encryption) ([]byte, error) { + encryptedKey := encryption.KeyData + // Create the block key from the current index + blockKeyBuf := createUInt32LEBuffer(blockKey) + var b bytes.Buffer + saltValue, err := base64.StdEncoding.DecodeString(encryptedKey.SaltValue) + if err != nil { + return nil, err + } + b.Write(saltValue) + b.Write(blockKeyBuf) + // Create the initialization vector by hashing the salt with the block key. + // Truncate or pad as needed to meet the block size. + iv := hashing(encryptedKey.HashAlgorithm, b.Bytes()) + if len(iv) < encryptedKey.BlockSize { + tmp := make([]byte, 0x36) + iv = append(iv, tmp...) + iv = tmp + } else if len(iv) > encryptedKey.BlockSize { + iv = iv[0:encryptedKey.BlockSize] + } + return iv, nil +} |