summaryrefslogtreecommitdiff
path: root/crypt.go
diff options
context:
space:
mode:
authorxuri <xuri.me@gmail.com>2022-05-29 19:37:10 +0800
committerxuri <xuri.me@gmail.com>2022-05-29 19:37:10 +0800
commit7a6d5f5ebe5fa9b74ec58b79baf79b369d496e21 (patch)
treeda761d89ba9c8eb4a78a69770532a1c87c255e4a /crypt.go
parent551b83afab85f2911410a6fa994fe5cc883d8804 (diff)
This initialized support for encryption workbook by password, ref #199
- Remove exported variable `ErrEncrypt`
Diffstat (limited to 'crypt.go')
-rw-r--r--crypt.go907
1 files changed, 760 insertions, 147 deletions
diff --git a/crypt.go b/crypt.go
index da9feb4..65d5dae 100644
--- a/crypt.go
+++ b/crypt.go
@@ -15,7 +15,6 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
- "crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
@@ -25,6 +24,7 @@ import (
"encoding/binary"
"encoding/xml"
"hash"
+ "math"
"reflect"
"strings"
@@ -36,17 +36,11 @@ import (
var (
blockKey = []byte{0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6} // Block keys used for encryption
- blockKeyHmacKey = []byte{0x5f, 0xb2, 0xad, 0x01, 0x0c, 0xb9, 0xe1, 0xf6}
- blockKeyHmacValue = []byte{0xa0, 0x67, 0x7f, 0x02, 0xb2, 0x2c, 0x84, 0x33}
- blockKeyVerifierHashInput = []byte{0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79}
- blockKeyVerifierHashValue = []byte{0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x34, 0x4e}
- packageOffset = 8 // First 8 bytes are the size of the stream
- packageEncryptionChunkSize = 4096
+ oleIdentifier = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}
iterCount = 50000
+ packageEncryptionChunkSize = 4096
+ packageOffset = 8 // First 8 bytes are the size of the stream
sheetProtectionSpinCount = 1e5
- oleIdentifier = []byte{
- 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
- }
)
// Encryption specifies the encryption structure, streams, and storages are
@@ -128,6 +122,14 @@ type StandardEncryptionVerifier struct {
EncryptedVerifierHash []byte
}
+// encryptionInfo structure is used for standard encryption with SHA1
+// cryptographic algorithm.
+type encryption struct {
+ BlockSize, SaltSize int
+ EncryptedKeyValue, EncryptedVerifierHashInput, EncryptedVerifierHashValue, SaltValue []byte
+ KeyBits uint32
+}
+
// Decrypt API decrypts 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.
@@ -154,125 +156,27 @@ func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
// Encrypt API encrypt data with the password.
func Encrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
- // Generate a random key to use to encrypt the document. Excel uses 32 bytes. We'll use the password to encrypt this key.
- packageKey, _ := randomBytes(32)
- keyDataSaltValue, _ := randomBytes(16)
- keyEncryptors, _ := randomBytes(16)
- encryptionInfo := Encryption{
- KeyData: KeyData{
- BlockSize: 16,
- KeyBits: len(packageKey) * 8,
- HashSize: 64,
- CipherAlgorithm: "AES",
- CipherChaining: "ChainingModeCBC",
- HashAlgorithm: "SHA512",
- SaltValue: base64.StdEncoding.EncodeToString(keyDataSaltValue),
- },
- KeyEncryptors: KeyEncryptors{
- KeyEncryptor: []KeyEncryptor{{
- EncryptedKey: EncryptedKey{
- SpinCount: 100000, KeyData: KeyData{
- CipherAlgorithm: "AES",
- CipherChaining: "ChainingModeCBC",
- HashAlgorithm: "SHA512",
- HashSize: 64,
- BlockSize: 16,
- KeyBits: 256,
- SaltValue: base64.StdEncoding.EncodeToString(keyEncryptors),
- },
- },
- }},
- },
- }
-
- // Package Encryption
-
- // Encrypt package using the package key.
- encryptedPackage, err := cryptPackage(true, packageKey, raw, encryptionInfo)
- if err != nil {
- return
- }
-
- // Data Integrity
-
- // Create the data integrity fields used by clients for integrity checks.
- // Generate a random array of bytes to use in HMAC. The docs say to use the same length as the key salt, but Excel seems to use 64.
- hmacKey, _ := randomBytes(64)
- if err != nil {
- return
- }
- // Create an initialization vector using the package encryption info and the appropriate block key.
- hmacKeyIV, err := createIV(blockKeyHmacKey, encryptionInfo)
- if err != nil {
- return
+ encryptor := encryption{
+ EncryptedVerifierHashInput: make([]byte, 16),
+ EncryptedVerifierHashValue: make([]byte, 32),
+ SaltValue: make([]byte, 16),
+ BlockSize: 16,
+ KeyBits: 128,
+ SaltSize: 16,
}
- // Use the package key and the IV to encrypt the HMAC key.
- encryptedHmacKey, _ := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, packageKey, hmacKeyIV, hmacKey)
- // Create the HMAC.
- h := hmac.New(sha512.New, append(hmacKey, encryptedPackage...))
- for _, buf := range [][]byte{hmacKey, encryptedPackage} {
- _, _ = h.Write(buf)
- }
- hmacValue := h.Sum(nil)
- // Generate an initialization vector for encrypting the resulting HMAC value.
- hmacValueIV, err := createIV(blockKeyHmacValue, encryptionInfo)
- if err != nil {
- return
- }
- // Encrypt the value.
- encryptedHmacValue, _ := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, packageKey, hmacValueIV, hmacValue)
- // Put the encrypted key and value on the encryption info.
- encryptionInfo.DataIntegrity.EncryptedHmacKey = base64.StdEncoding.EncodeToString(encryptedHmacKey)
- encryptionInfo.DataIntegrity.EncryptedHmacValue = base64.StdEncoding.EncodeToString(encryptedHmacValue)
-
// Key Encryption
-
- // Convert the password to an encryption key.
- key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo)
- if err != nil {
- return
- }
- // Encrypt the package key with the encryption key.
- encryptedKeyValue, _ := crypt(true, encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.CipherAlgorithm, encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.CipherChaining, key, keyEncryptors, packageKey)
- encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedKeyValue = base64.StdEncoding.EncodeToString(encryptedKeyValue)
-
- // Verifier hash
-
- // Create a random byte array for hashing.
- verifierHashInput, _ := randomBytes(16)
- // Create an encryption key from the password for the input.
- verifierHashInputKey, err := convertPasswdToKey(opt.Password, blockKeyVerifierHashInput, encryptionInfo)
- if err != nil {
- return
- }
- // Use the key to encrypt the verifier input.
- encryptedVerifierHashInput, err := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, verifierHashInputKey, keyEncryptors, verifierHashInput)
- if err != nil {
- return
- }
- encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedVerifierHashInput = base64.StdEncoding.EncodeToString(encryptedVerifierHashInput)
- // Create a hash of the input.
- verifierHashValue := hashing(encryptionInfo.KeyData.HashAlgorithm, verifierHashInput)
- // Create an encryption key from the password for the hash.
- verifierHashValueKey, err := convertPasswdToKey(opt.Password, blockKeyVerifierHashValue, encryptionInfo)
- if err != nil {
- return
- }
- // Use the key to encrypt the hash value.
- encryptedVerifierHashValue, err := crypt(true, encryptionInfo.KeyData.CipherAlgorithm, encryptionInfo.KeyData.CipherChaining, verifierHashValueKey, keyEncryptors, verifierHashValue)
+ encryptionInfoBuffer, err := encryptor.standardKeyEncryption(opt.Password)
if err != nil {
- return
- }
- encryptionInfo.KeyEncryptors.KeyEncryptor[0].EncryptedKey.EncryptedVerifierHashValue = base64.StdEncoding.EncodeToString(encryptedVerifierHashValue)
- // Marshal the encryption info buffer.
- encryptionInfoBuffer, err := xml.Marshal(encryptionInfo)
- if err != nil {
- return
+ return nil, err
}
- // TODO: Create a new CFB.
- _, _ = encryptedPackage, encryptionInfoBuffer
- err = ErrEncrypt
- return
+ // Package Encryption
+ encryptedPackage := make([]byte, 8)
+ binary.LittleEndian.PutUint64(encryptedPackage, uint64(len(raw)))
+ encryptedPackage = append(encryptedPackage, encryptor.encrypt(raw)...)
+ // Create a new CFB
+ compoundFile := cfb{}
+ packageBuf = compoundFile.Writer(encryptionInfoBuffer, encryptedPackage)
+ return packageBuf, nil
}
// extractPart extract data from storage by specified part name.
@@ -419,6 +323,68 @@ func standardXORBytes(a, b []byte) []byte {
return buf
}
+// encrypt provides a function to encrypt given value with AES cryptographic
+// algorithm.
+func (e *encryption) encrypt(input []byte) []byte {
+ inputBytes := len(input)
+ if pad := inputBytes % e.BlockSize; pad != 0 {
+ inputBytes += e.BlockSize - pad
+ }
+ var output, chunk []byte
+ encryptedChunk := make([]byte, e.BlockSize)
+ for i := 0; i < inputBytes; i += e.BlockSize {
+ if i+e.BlockSize <= len(input) {
+ chunk = input[i : i+e.BlockSize]
+ } else {
+ chunk = input[i:]
+ }
+ chunk = append(chunk, make([]byte, e.BlockSize-len(chunk))...)
+ c, _ := aes.NewCipher(e.EncryptedKeyValue)
+ c.Encrypt(encryptedChunk, chunk)
+ output = append(output, encryptedChunk...)
+ }
+ return output
+}
+
+// standardKeyEncryption encrypt convert the password to an encryption key.
+func (e *encryption) standardKeyEncryption(password string) ([]byte, error) {
+ if len(password) == 0 || len(password) > MaxFieldLength {
+ return nil, ErrPasswordLengthInvalid
+ }
+ var storage cfb
+ storage.writeUint16(0x0003)
+ storage.writeUint16(0x0002)
+ storage.writeUint32(0x24)
+ storage.writeUint32(0xA4)
+ storage.writeUint32(0x24)
+ storage.writeUint32(0x00)
+ storage.writeUint32(0x660E)
+ storage.writeUint32(0x8004)
+ storage.writeUint32(0x80)
+ storage.writeUint32(0x18)
+ storage.writeUint64(0x00)
+ providerName := "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)"
+ storage.writeStrings(providerName)
+ storage.writeUint16(0x00)
+ storage.writeUint32(0x10)
+ keyDataSaltValue, _ := randomBytes(16)
+ verifierHashInput, _ := randomBytes(16)
+ e.SaltValue = keyDataSaltValue
+ e.EncryptedKeyValue, _ = standardConvertPasswdToKey(
+ StandardEncryptionHeader{KeySize: e.KeyBits},
+ StandardEncryptionVerifier{Salt: e.SaltValue},
+ &Options{Password: password})
+ verifierHashInputKey := hashing("sha1", verifierHashInput)
+ e.EncryptedVerifierHashInput = e.encrypt(verifierHashInput)
+ e.EncryptedVerifierHashValue = e.encrypt(verifierHashInputKey)
+ storage.writeBytes(e.SaltValue)
+ storage.writeBytes(e.EncryptedVerifierHashInput)
+ storage.writeUint32(0x14)
+ storage.writeBytes(e.EncryptedVerifierHashValue)
+ storage.position = 0
+ return storage.stream, nil
+}
+
// ECMA-376 Agile Encryption
// agileDecrypt decrypt the CFB file format with ECMA-376 agile encryption.
@@ -444,9 +410,9 @@ func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (
if err != nil {
return
}
- packageKey, _ := crypt(false, encryptedKey.CipherAlgorithm, encryptedKey.CipherChaining, key, saltValue, encryptedKeyValue)
+ packageKey, _ := decrypt(key, saltValue, encryptedKeyValue)
// Use the package key to decrypt the package.
- return cryptPackage(false, packageKey, encryptedPackageBuf, encryptionInfo)
+ return decryptPackage(packageKey, encryptedPackageBuf, encryptionInfo)
}
// convertPasswdToKey convert the password into an encryption key.
@@ -519,30 +485,21 @@ func parseEncryptionInfo(encryptionInfo []byte) (encryption Encryption, err erro
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) {
+// decrypt provides a function to decrypt input by given cipher algorithm,
+// cipher chaining, key and initialization vector.
+func decrypt(key, iv, input []byte) (packageKey []byte, err error) {
block, err := aes.NewCipher(key)
if err != nil {
return input, err
}
- var stream cipher.BlockMode
- if encrypt {
- stream = cipher.NewCBCEncrypter(block, iv)
- } else {
- stream = cipher.NewCBCDecrypter(block, iv)
- }
- stream.CryptBlocks(input, input)
+ cipher.NewCBCDecrypter(block, iv).CryptBlocks(input, input)
return input, nil
}
-// cryptPackage encrypt / decrypt package by given packageKey and encryption
+// decryptPackage decrypt package by given packageKey and encryption
// info.
-func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) {
+func decryptPackage(packageKey, input []byte, encryption Encryption) (outputChunks []byte, err error) {
encryptedKey, offset := encryption.KeyData, packageOffset
- if encrypt {
- offset = 0
- }
var i, start, end int
var iv, outputChunk []byte
for end < len(input) {
@@ -570,17 +527,14 @@ func cryptPackage(encrypt bool, packageKey, input []byte, encryption 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)
+ // Decrypt the chunk and add it to the array
+ outputChunk, err = decrypt(packageKey, iv, inputChunk)
if err != nil {
return
}
outputChunks = append(outputChunks, outputChunk...)
i++
}
- if encrypt {
- outputChunks = append(createUInt32LEBuffer(len(input), 8), outputChunks...)
- }
return
}
@@ -662,3 +616,662 @@ func genISOPasswdHash(passwd, hashAlgorithm, salt string, spinCount int) (hashVa
hashValue, saltValue = base64.StdEncoding.EncodeToString(key), base64.StdEncoding.EncodeToString(s)
return
}
+
+// Compound File Binary Implements
+
+// cfb structure is used for the compound file binary (CFB) file format writer.
+type cfb struct {
+ stream []byte
+ position int
+}
+
+// writeBytes write bytes in the stream by a given value with an offset.
+func (c *cfb) writeBytes(value []byte) {
+ pos := c.position
+ for i := 0; i < len(value); i++ {
+ for j := len(c.stream); j <= i+pos; j++ {
+ c.stream = append(c.stream, 0)
+ }
+ c.stream[i+pos] = value[i]
+ }
+ c.position = pos + len(value)
+}
+
+// writeUint16 write an uint16 data type bytes in the stream by a given value
+// with an offset.
+func (c *cfb) writeUint16(value int) {
+ buf := make([]byte, 2)
+ binary.LittleEndian.PutUint16(buf, uint16(value))
+ c.writeBytes(buf)
+}
+
+// writeUint32 write an uint32 data type bytes in the stream by a given value
+// with an offset.
+func (c *cfb) writeUint32(value int) {
+ buf := make([]byte, 4)
+ binary.LittleEndian.PutUint32(buf, uint32(value))
+ c.writeBytes(buf)
+}
+
+// writeUint64 write an uint64 data type bytes in the stream by a given value
+// with an offset.
+func (c *cfb) writeUint64(value int) {
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint64(buf, uint64(value))
+ c.writeBytes(buf)
+}
+
+// writeBytes write strings in the stream by a given value with an offset.
+func (c *cfb) writeStrings(value string) {
+ encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
+ buffer, err := encoder.Bytes([]byte(value))
+ if err != nil {
+ return
+ }
+ c.writeBytes(buffer)
+}
+
+// writeVersionStream provides a function to write compound file version
+// stream.
+func (c *cfb) writeVersionStream() []byte {
+ var storage cfb
+ storage.writeUint32(0x3c)
+ storage.writeStrings("Microsoft.Container.DataSpaces")
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x01)
+ return storage.stream
+}
+
+// writeDataSpaceMapStream provides a function to write compound file
+// DataSpaceMap stream.
+func (c *cfb) writeDataSpaceMapStream() []byte {
+ var storage cfb
+ storage.writeUint32(0x08)
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x68)
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x00)
+ storage.writeUint32(0x20)
+ storage.writeStrings("EncryptedPackage")
+ storage.writeUint32(0x32)
+ storage.writeStrings("StrongEncryptionDataSpace")
+ storage.writeUint16(0x00)
+ return storage.stream
+}
+
+// writeStrongEncryptionDataSpaceStream provides a function to write compound
+// file StrongEncryptionDataSpace stream.
+func (c *cfb) writeStrongEncryptionDataSpaceStream() []byte {
+ var storage cfb
+ storage.writeUint32(0x08)
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x32)
+ storage.writeStrings("StrongEncryptionTransform")
+ storage.writeUint16(0x00)
+ return storage.stream
+}
+
+// writePrimaryStream provides a function to write compound file Primary
+// stream.
+func (c *cfb) writePrimaryStream() []byte {
+ var storage cfb
+ storage.writeUint32(0x6C)
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x4C)
+ storage.writeStrings("{FF9A3F03-56EF-4613-BDD5-5A41C1D07246}")
+ storage.writeUint32(0x4E)
+ storage.writeUint16(0x00)
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x01)
+ storage.writeUint32(0x01)
+ storage.writeStrings("AES128")
+ storage.writeUint32(0x00)
+ storage.writeUint32(0x04)
+ return storage.stream
+}
+
+// writeFileStream provides a function to write encrypted package in compound
+// file by a given buffer and the short sector allocation table.
+func (c *cfb) writeFileStream(encryptionInfoBuffer []byte, SSAT []int) ([]byte, []int) {
+ var (
+ storage cfb
+ miniProperties int
+ stream = make([]byte, 0x100)
+ )
+ if encryptionInfoBuffer != nil {
+ copy(stream, encryptionInfoBuffer)
+ }
+ storage.writeBytes(stream)
+ streamBlocks := len(stream) / 64
+ if len(stream)%64 > 0 {
+ streamBlocks++
+ }
+ for i := 1; i < streamBlocks; i++ {
+ SSAT = append(SSAT, i)
+ }
+ SSAT = append(SSAT, -2)
+ miniProperties += streamBlocks
+ versionStream := make([]byte, 0x80)
+ version := c.writeVersionStream()
+ copy(versionStream, version)
+ storage.writeBytes(versionStream)
+ versionBlocks := len(versionStream) / 64
+ if len(versionStream)%64 > 0 {
+ versionBlocks++
+ }
+ for i := 1; i < versionBlocks; i++ {
+ SSAT = append(SSAT, i+miniProperties)
+ }
+ SSAT = append(SSAT, -2)
+ miniProperties += versionBlocks
+ dataSpaceMap := make([]byte, 0x80)
+ dataStream := c.writeDataSpaceMapStream()
+ copy(dataSpaceMap, dataStream)
+ storage.writeBytes(dataSpaceMap)
+ dataSpaceMapBlocks := len(dataSpaceMap) / 64
+ if len(dataSpaceMap)%64 > 0 {
+ dataSpaceMapBlocks++
+ }
+ for i := 1; i < dataSpaceMapBlocks; i++ {
+ SSAT = append(SSAT, i+miniProperties)
+ }
+ SSAT = append(SSAT, -2)
+ miniProperties += dataSpaceMapBlocks
+ dataSpaceStream := c.writeStrongEncryptionDataSpaceStream()
+ storage.writeBytes(dataSpaceStream)
+ dataSpaceStreamBlocks := len(dataSpaceStream) / 64
+ if len(dataSpaceStream)%64 > 0 {
+ dataSpaceStreamBlocks++
+ }
+ for i := 1; i < dataSpaceStreamBlocks; i++ {
+ SSAT = append(SSAT, i+miniProperties)
+ }
+ SSAT = append(SSAT, -2)
+ miniProperties += dataSpaceStreamBlocks
+ primaryStream := make([]byte, 0x1C0)
+ primary := c.writePrimaryStream()
+ copy(primaryStream, primary)
+ storage.writeBytes(primaryStream)
+ primaryBlocks := len(primary) / 64
+ if len(primary)%64 > 0 {
+ primaryBlocks++
+ }
+ for i := 1; i < primaryBlocks; i++ {
+ SSAT = append(SSAT, i+miniProperties)
+ }
+ SSAT = append(SSAT, -2)
+ if len(SSAT) < 128 {
+ for i := len(SSAT); i < 128; i++ {
+ SSAT = append(SSAT, -1)
+ }
+ }
+ storage.position = 0
+ return storage.stream, SSAT
+}
+
+// writeRootEntry provides a function to write compound file root directory
+// entry. The first entry in the first sector of the directory chain
+// (also referred to as the first element of the directory array, or stream
+// ID #0) is known as the root directory entry, and it is reserved for two
+// purposes. First, it provides a root parent for all objects that are
+// stationed at the root of the compound file. Second, its function is
+// overloaded to store the size and starting sector for the mini stream.
+func (c *cfb) writeRootEntry(customSectID int) []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("Root Entry")
+ storage.position = 0x40
+ storage.writeUint16(0x16)
+ storage.writeBytes([]byte{5, 0})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(1)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(customSectID)
+ storage.writeUint32(0x340)
+ return storage.stream
+}
+
+// writeEncryptionInfo provides a function to write compound file
+// writeEncryptionInfo stream. The writeEncryptionInfo stream contains
+// detailed information that is used to initialize the cryptography used to
+// encrypt the EncryptedPackage stream.
+func (c *cfb) writeEncryptionInfo() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("EncryptionInfo")
+ storage.position = 0x40
+ storage.writeUint16(0x1E)
+ storage.writeBytes([]byte{2, 1})
+ storage.writeUint32(0x03)
+ storage.writeUint32(0x02)
+ storage.writeUint32(-1)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0xF8)
+ return storage.stream
+}
+
+// writeEncryptedPackage provides a function to write compound file
+// writeEncryptedPackage stream. The writeEncryptedPackage stream is an
+// encrypted stream of bytes containing the entire ECMA-376 source file in
+// compressed form.
+func (c *cfb) writeEncryptedPackage(propertyCount, size int) []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("EncryptedPackage")
+ storage.position = 0x40
+ storage.writeUint16(0x22)
+ storage.writeBytes([]byte{2, 0})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(propertyCount)
+ storage.writeUint32(size)
+ return storage.stream
+}
+
+// writeDataSpaces provides a function to write compound file writeDataSpaces
+// stream. The data spaces structure consists of a set of interrelated
+// storages and streams in an OLE compound file.
+func (c *cfb) writeDataSpaces() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeUint16(0x06)
+ storage.position = 0x40
+ storage.writeUint16(0x18)
+ storage.writeBytes([]byte{1, 0})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(5)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ return storage.stream
+}
+
+// writeVersion provides a function to write compound file version. The
+// writeVersion structure specifies the version of a product or feature. It
+// contains a major and a minor version number.
+func (c *cfb) writeVersion() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("Version")
+ storage.position = 0x40
+ storage.writeUint16(0x10)
+ storage.writeBytes([]byte{2, 1})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(4)
+ storage.writeUint32(76)
+ return storage.stream
+}
+
+// writeDataSpaceMap provides a function to write compound file
+// writeDataSpaceMap stream. The writeDataSpaceMap structure associates
+// protected content with data space definitions. The data space definition,
+// in turn, describes the series of transforms that MUST be applied to that
+// protected content to restore it to its original form. By using a map to
+// associate data space definitions with content, a single data space
+// definition can be used to define the transforms applied to more than one
+// piece of protected content. However, a given piece of protected content can
+// be referenced only by a single data space definition.
+func (c *cfb) writeDataSpaceMap() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("DataSpaceMap")
+ storage.position = 0x40
+ storage.writeUint16(0x1A)
+ storage.writeBytes([]byte{2, 1})
+ storage.writeUint32(0x04)
+ storage.writeUint32(0x06)
+ storage.writeUint32(-1)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(6)
+ storage.writeUint32(112)
+ return storage.stream
+}
+
+// writeDataSpaceInfo provides a function to write compound file
+// writeDataSpaceInfo storage. The writeDataSpaceInfo is a storage containing
+// the data space definitions used in the file. This storage must contain one
+// or more streams, each of which contains a DataSpaceDefinition structure.
+// The storage must contain exactly one stream for each DataSpaceMapEntry
+// structure in the DataSpaceMap stream. The name of each stream must be equal
+// to the DataSpaceName field of exactly one DataSpaceMapEntry structure
+// contained in the DataSpaceMap stream.
+func (c *cfb) writeDataSpaceInfo() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("DataSpaceInfo")
+ storage.position = 0x40
+ storage.writeUint16(0x1C)
+ storage.writeBytes([]byte{1, 1})
+ storage.writeUint32(-1)
+ storage.writeUint32(8)
+ storage.writeUint32(7)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ return storage.stream
+}
+
+// writeStrongEncryptionDataSpace provides a function to write compound file
+// writeStrongEncryptionDataSpace stream.
+func (c *cfb) writeStrongEncryptionDataSpace() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("StrongEncryptionDataSpace")
+ storage.position = 0x40
+ storage.writeUint16(0x34)
+ storage.writeBytes([]byte{2, 1})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(8)
+ storage.writeUint32(64)
+ return storage.stream
+}
+
+// writeTransformInfo provides a function to write compound file
+// writeTransformInfo storage. writeTransformInfo is a storage containing
+// definitions for the transforms used in the data space definitions stored in
+// the DataSpaceInfo storage. The stream contains zero or more definitions for
+// the possible transforms that can be applied to the data in content
+// streams.
+func (c *cfb) writeTransformInfo() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("TransformInfo")
+ storage.position = 0x40
+ storage.writeUint16(0x1C)
+ storage.writeBytes([]byte{1, 0})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(9)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ return storage.stream
+}
+
+// writeStrongEncryptionTransform provides a function to write compound file
+// writeStrongEncryptionTransform storage.
+func (c *cfb) writeStrongEncryptionTransform() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeStrings("StrongEncryptionTransform")
+ storage.position = 0x40
+ storage.writeUint16(0x34)
+ storage.writeBytes([]byte{1})
+ storage.writeBytes([]byte{1})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(0x0A)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ return storage.stream
+}
+
+// writePrimary provides a function to write compound file writePrimary stream.
+func (c *cfb) writePrimary() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.writeUint16(0x06)
+ storage.writeStrings("Primary")
+ storage.position = 0x40
+ storage.writeUint16(0x12)
+ storage.writeBytes([]byte{2, 1})
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.position = 0x64
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(9)
+ storage.writeUint32(208)
+ return storage.stream
+}
+
+// writeNoneDir provides a function to write compound file writeNoneDir stream.
+func (c *cfb) writeNoneDir() []byte {
+ storage := cfb{stream: make([]byte, 128)}
+ storage.position = 0x40
+ storage.writeUint16(0x00)
+ storage.writeUint16(0x00)
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ storage.writeUint32(-1)
+ return storage.stream
+}
+
+// writeDirectoryEntry provides a function to write compound file directory
+// entries. The directory entry array is an array of directory entries that
+// are grouped into a directory sector. Each storage object or stream object
+// within a compound file is represented by a single directory entry. The
+// space for the directory sectors that are holding the array is allocated
+// from the FAT.
+func (c *cfb) writeDirectoryEntry(propertyCount, customSectID, size int) []byte {
+ var storage cfb
+ if size < 0 {
+ size = 0
+ }
+ for _, entry := range [][]byte{
+ c.writeRootEntry(customSectID),
+ c.writeEncryptionInfo(),
+ c.writeEncryptedPackage(propertyCount, size),
+ c.writeDataSpaces(),
+ c.writeVersion(),
+ c.writeDataSpaceMap(),
+ c.writeDataSpaceInfo(),
+ c.writeStrongEncryptionDataSpace(),
+ c.writeTransformInfo(),
+ c.writeStrongEncryptionTransform(),
+ c.writePrimary(),
+ c.writeNoneDir(),
+ } {
+ storage.writeBytes(entry)
+ }
+ return storage.stream
+}
+
+// writeMSAT provides a function to write compound file sector allocation
+// table.
+func (c *cfb) writeMSAT(MSATBlocks, SATBlocks int, MSAT []int) []int {
+ if MSATBlocks > 0 {
+ cnt, MSATIdx := MSATBlocks*128+109, 0
+ for i := 0; i < cnt; i++ {
+ if i < SATBlocks {
+ bufferSize := i - 109
+ if bufferSize > 0 && bufferSize%0x80 == 0 {
+ MSATIdx++
+ MSAT = append(MSAT, MSATIdx)
+ }
+ MSAT = append(MSAT, i+MSATBlocks)
+ continue
+ }
+ MSAT = append(MSAT, -1)
+ }
+ } else {
+ for i := 0; i < 109; i++ {
+ if i < SATBlocks {
+ MSAT = append(MSAT, i)
+ continue
+ }
+ MSAT = append(MSAT, -1)
+ }
+ }
+ return MSAT
+}
+
+// writeSAT provides a function to write compound file master sector allocation
+// table.
+func (c *cfb) writeSAT(MSATBlocks, SATBlocks, SSATBlocks, directoryBlocks, fileBlocks, streamBlocks int, SAT []int) (int, []int) {
+ var blocks int
+ if SATBlocks > 0 {
+ for i := 1; i <= MSATBlocks; i++ {
+ SAT = append(SAT, -4)
+ }
+ blocks = MSATBlocks
+ for i := 1; i <= SATBlocks; i++ {
+ SAT = append(SAT, -3)
+ }
+ blocks += SATBlocks
+ for i := 1; i < SSATBlocks; i++ {
+ SAT = append(SAT, i)
+ }
+ SAT = append(SAT, -2)
+ blocks += SSATBlocks
+ for i := 1; i < directoryBlocks; i++ {
+ SAT = append(SAT, i+blocks)
+ }
+ SAT = append(SAT, -2)
+ blocks += directoryBlocks
+ for i := 1; i < fileBlocks; i++ {
+ SAT = append(SAT, i+blocks)
+ }
+ SAT = append(SAT, -2)
+ blocks += fileBlocks
+ for i := 1; i < streamBlocks; i++ {
+ SAT = append(SAT, i+blocks)
+ }
+ SAT = append(SAT, -2)
+ }
+ return blocks, SAT
+}
+
+// Writer provides a function to create compound file with given info stream
+// and package stream.
+//
+// MSAT - The master sector allocation table
+// SSAT - The short sector allocation table
+// SAT - The sector allocation table
+//
+func (c *cfb) Writer(encryptionInfoBuffer, encryptedPackage []byte) []byte {
+ var (
+ storage cfb
+ MSAT, SAT, SSAT []int
+ directoryBlocks, fileBlocks, SSATBlocks = 3, 2, 1
+ size = int(math.Max(float64(len(encryptedPackage)), float64(packageEncryptionChunkSize)))
+ streamBlocks = len(encryptedPackage) / 0x200
+ )
+ if len(encryptedPackage)%0x200 > 0 {
+ streamBlocks++
+ }
+ propertyBlocks := directoryBlocks + fileBlocks + SSATBlocks
+ blockSize := (streamBlocks + propertyBlocks) * 4
+ SATBlocks := blockSize / 0x200
+ if blockSize%0x200 > 0 {
+ SATBlocks++
+ }
+ MSATBlocks, blocksChanged := 0, true
+ for blocksChanged {
+ var SATCap, MSATCap int
+ blocksChanged = false
+ blockSize = (streamBlocks + propertyBlocks + SATBlocks + MSATBlocks) * 4
+ SATCap = blockSize / 0x200
+ if blockSize%0x200 > 0 {
+ SATCap++
+ }
+ if SATCap > SATBlocks {
+ SATBlocks, blocksChanged = SATCap, true
+ continue
+ }
+ if SATBlocks > 109 {
+ blockRemains := (SATBlocks - 109) * 4
+ blockBuffer := blockRemains % 0x200
+ MSATCap = blockRemains / 0x200
+ if blockBuffer > 0 {
+ MSATCap++
+ }
+ if blockBuffer+(4*MSATCap) > 0x200 {
+ MSATCap++
+ }
+ if MSATCap > MSATBlocks {
+ MSATBlocks, blocksChanged = MSATCap, true
+ }
+ }
+ }
+ MSAT = c.writeMSAT(MSATBlocks, SATBlocks, MSAT)
+ blocks, SAT := c.writeSAT(MSATBlocks, SATBlocks, SSATBlocks, directoryBlocks, fileBlocks, streamBlocks, SAT)
+ storage.writeUint32(0xE011CFD0)
+ storage.writeUint32(0xE11AB1A1)
+ storage.writeUint64(0x00)
+ storage.writeUint64(0x00)
+ storage.writeUint16(0x003E)
+ storage.writeUint16(0x0003)
+ storage.writeUint16(-2)
+ storage.writeUint16(9)
+ storage.writeUint32(6)
+ storage.writeUint32(0)
+ storage.writeUint32(0)
+ storage.writeUint32(SATBlocks)
+ storage.writeUint32(MSATBlocks + SATBlocks + SSATBlocks)
+ storage.writeUint32(0)
+ storage.writeUint32(0x00001000)
+ storage.writeUint32(SATBlocks + MSATBlocks)
+ storage.writeUint32(SSATBlocks)
+ if MSATBlocks > 0 {
+ storage.writeUint32(0)
+ storage.writeUint32(MSATBlocks)
+ } else {
+ storage.writeUint32(-2)
+ storage.writeUint32(0)
+ }
+ for _, block := range MSAT {
+ storage.writeUint32(block)
+ }
+ for i := 0; i < SATBlocks*128; i++ {
+ if i < len(SAT) {
+ storage.writeUint32(SAT[i])
+ continue
+ }
+ storage.writeUint32(-1)
+ }
+ fileStream, SSATStream := c.writeFileStream(encryptionInfoBuffer, SSAT)
+ for _, block := range SSATStream {
+ storage.writeUint32(block)
+ }
+ directoryEntry := c.writeDirectoryEntry(blocks, blocks-fileBlocks, size)
+ storage.writeBytes(directoryEntry)
+ storage.writeBytes(fileStream)
+ storage.writeBytes(encryptedPackage)
+ return storage.stream
+}