forked from p30928647/excelize
init ECMA-376 agile encryption support
This commit is contained in:
parent
1111de2fdb
commit
01afc6e03f
181
crypt.go
181
crypt.go
|
@ -13,6 +13,7 @@ import (
|
|||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
|
@ -22,6 +23,8 @@ import (
|
|||
"encoding/xml"
|
||||
"errors"
|
||||
"hash"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/richardlehane/mscfb"
|
||||
|
@ -32,7 +35,11 @@ import (
|
|||
|
||||
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
|
||||
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
|
||||
iterCount = 50000
|
||||
cryptoIdentifier = []byte{ // checking protect workbook by [MS-OFFCRYPTO] - v20181211 3.1 FeatureIdentifier
|
||||
|
@ -50,6 +57,7 @@ var (
|
|||
// Encryption specifies the encryption structure, streams, and storages are
|
||||
// required when encrypting ECMA-376 documents.
|
||||
type Encryption struct {
|
||||
XMLName xml.Name `xml:"encryption"`
|
||||
KeyData KeyData `xml:"keyData"`
|
||||
DataIntegrity DataIntegrity `xml:"dataIntegrity"`
|
||||
KeyEncryptors KeyEncryptors `xml:"keyEncryptors"`
|
||||
|
@ -150,6 +158,125 @@ func Decrypt(raw []byte, opt *Options) (packageBuf []byte, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// Use the package key and the IV to encrypt the HMAC key.
|
||||
encryptedHmacKey, err := 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, err := 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, err := 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)
|
||||
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
|
||||
}
|
||||
// TODO: Create a new CFB.
|
||||
_, _ = encryptedPackage, encryptionInfoBuffer
|
||||
err = errors.New("not support encryption currently")
|
||||
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() {
|
||||
|
@ -265,11 +392,11 @@ func standardConvertPasswdToKey(header StandardEncryptionHeader, verifier Standa
|
|||
}
|
||||
key := hashing("sha1", verifier.Salt, passwordBuffer)
|
||||
for i := 0; i < iterCount; i++ {
|
||||
iterator := createUInt32LEBuffer(i)
|
||||
iterator := createUInt32LEBuffer(i, 4)
|
||||
key = hashing("sha1", iterator, key)
|
||||
}
|
||||
var block int
|
||||
hfinal := hashing("sha1", key, createUInt32LEBuffer(block))
|
||||
hfinal := hashing("sha1", key, createUInt32LEBuffer(block, 4))
|
||||
cbRequiredKeyLength := int(header.KeySize) / 8
|
||||
cbHash := sha1.Size
|
||||
buf1 := bytes.Repeat([]byte{0x36}, 64)
|
||||
|
@ -299,15 +426,14 @@ func standardXORBytes(a, b []byte) []byte {
|
|||
// 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.
|
||||
// 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)
|
||||
key, err := convertPasswdToKey(opt.Password, blockKey, encryptionInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -327,7 +453,7 @@ func agileDecrypt(encryptionInfoBuf, encryptedPackageBuf []byte, opt *Options) (
|
|||
}
|
||||
|
||||
// convertPasswdToKey convert the password into an encryption key.
|
||||
func convertPasswdToKey(passwd string, encryption Encryption) (key []byte, err error) {
|
||||
func convertPasswdToKey(passwd string, blockKey []byte, encryption Encryption) (key []byte, err error) {
|
||||
var b bytes.Buffer
|
||||
saltValue, err := base64.StdEncoding.DecodeString(encryption.KeyEncryptors.KeyEncryptor[0].EncryptedKey.SaltValue)
|
||||
if err != nil {
|
||||
|
@ -344,7 +470,7 @@ func convertPasswdToKey(passwd string, encryption Encryption) (key []byte, err e
|
|||
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)
|
||||
iterator := createUInt32LEBuffer(i, 4)
|
||||
key = hashing(encryption.KeyData.HashAlgorithm, iterator, key)
|
||||
}
|
||||
// Now generate the final hash.
|
||||
|
@ -385,8 +511,8 @@ func hashing(hashAlgorithm string, buffer ...[]byte) (key []byte) {
|
|||
|
||||
// createUInt32LEBuffer create buffer with little endian 32-bit unsigned
|
||||
// integer.
|
||||
func createUInt32LEBuffer(value int) []byte {
|
||||
buf := make([]byte, 4)
|
||||
func createUInt32LEBuffer(value int, bufferSize int) []byte {
|
||||
buf := make([]byte, bufferSize)
|
||||
binary.LittleEndian.PutUint32(buf, uint32(value))
|
||||
return buf
|
||||
}
|
||||
|
@ -404,7 +530,12 @@ func crypt(encrypt bool, cipherAlgorithm, cipherChaining string, key, iv, input
|
|||
if err != nil {
|
||||
return input, err
|
||||
}
|
||||
stream := cipher.NewCBCDecrypter(block, iv)
|
||||
var stream cipher.BlockMode
|
||||
if encrypt {
|
||||
stream = cipher.NewCBCEncrypter(block, iv)
|
||||
} else {
|
||||
stream = cipher.NewCBCDecrypter(block, iv)
|
||||
}
|
||||
stream.CryptBlocks(input, input)
|
||||
return input, nil
|
||||
}
|
||||
|
@ -440,7 +571,7 @@ func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption)
|
|||
inputChunk = append(inputChunk, make([]byte, encryptedKey.BlockSize-remainder)...)
|
||||
}
|
||||
// Create the initialization vector
|
||||
iv, err = createIV(encrypt, i, encryption)
|
||||
iv, err = createIV(i, encryption)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -452,24 +583,29 @@ func cryptPackage(encrypt bool, packageKey, input []byte, encryption Encryption)
|
|||
outputChunks = append(outputChunks, outputChunk...)
|
||||
i++
|
||||
}
|
||||
if encrypt {
|
||||
outputChunks = append(createUInt32LEBuffer(len(input), 8), outputChunks...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// createIV create an initialization vector (IV).
|
||||
func createIV(encrypt bool, blockKey int, encryption Encryption) ([]byte, error) {
|
||||
func createIV(blockKey interface{}, encryption Encryption) ([]byte, error) {
|
||||
encryptedKey := encryption.KeyData
|
||||
// Create the block key from the current index
|
||||
blockKeyBuf := createUInt32LEBuffer(blockKey)
|
||||
var b bytes.Buffer
|
||||
var blockKeyBuf []byte
|
||||
if reflect.TypeOf(blockKey).Kind() == reflect.Int {
|
||||
blockKeyBuf = createUInt32LEBuffer(blockKey.(int), 4)
|
||||
} else {
|
||||
blockKeyBuf = blockKey.([]byte)
|
||||
}
|
||||
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())
|
||||
iv := hashing(encryptedKey.HashAlgorithm, append(saltValue, blockKeyBuf...))
|
||||
if len(iv) < encryptedKey.BlockSize {
|
||||
tmp := make([]byte, 0x36)
|
||||
iv = append(iv, tmp...)
|
||||
|
@ -479,3 +615,12 @@ func createIV(encrypt bool, blockKey int, encryption Encryption) ([]byte, error)
|
|||
}
|
||||
return iv, nil
|
||||
}
|
||||
|
||||
// randomBytes returns securely generated random bytes. It will return an error if the system's
|
||||
// secure random number generator fails to function correctly, in which case the caller should not
|
||||
// continue.
|
||||
func randomBytes(n int) ([]byte, error) {
|
||||
b := make([]byte, n)
|
||||
_, err := rand.Read(b)
|
||||
return b, err
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// 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 (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncrypt(t *testing.T) {
|
||||
f, err := OpenFile(filepath.Join("test", "encryptSHA1.xlsx"), Options{Password: "password"})
|
||||
assert.NoError(t, err)
|
||||
assert.EqualError(t, f.SaveAs(filepath.Join("test", "TestEncrypt.xlsx"), Options{Password: "password"}), "not support encryption currently")
|
||||
}
|
14
excelize.go
14
excelize.go
|
@ -32,6 +32,7 @@ import (
|
|||
// File define a populated spreadsheet file struct.
|
||||
type File struct {
|
||||
sync.Mutex
|
||||
options *Options
|
||||
xmlAttr map[string][]xml.Attr
|
||||
checked map[string]bool
|
||||
sheetMap map[string]string
|
||||
|
@ -75,11 +76,7 @@ func OpenFile(filename string, opt ...Options) (*File, error) {
|
|||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
var option Options
|
||||
for _, o := range opt {
|
||||
option = o
|
||||
}
|
||||
f, err := OpenReader(file, option)
|
||||
f, err := OpenReader(file, opt...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -111,12 +108,12 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := newFile()
|
||||
if bytes.Contains(b, oleIdentifier) {
|
||||
var option Options
|
||||
for _, o := range opt {
|
||||
option = o
|
||||
f.options = &o
|
||||
}
|
||||
b, err = Decrypt(b, &option)
|
||||
b, err = Decrypt(b, f.options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decrypted file failed")
|
||||
}
|
||||
|
@ -130,7 +127,6 @@ func OpenReader(r io.Reader, opt ...Options) (*File, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := newFile()
|
||||
f.SheetCount, f.XLSX = sheetCount, file
|
||||
f.CalcChain = f.calcChainReader()
|
||||
f.sheetMap = f.getSheetMap()
|
||||
|
|
18
file.go
18
file.go
|
@ -64,7 +64,7 @@ func (f *File) Save() error {
|
|||
|
||||
// SaveAs provides a function to create or update to an xlsx file at the
|
||||
// provided path.
|
||||
func (f *File) SaveAs(name string) error {
|
||||
func (f *File) SaveAs(name string, opt ...Options) error {
|
||||
if len(name) > FileNameLength {
|
||||
return errors.New("file name length exceeds maximum limit")
|
||||
}
|
||||
|
@ -73,6 +73,9 @@ func (f *File) SaveAs(name string) error {
|
|||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
for _, o := range opt {
|
||||
f.options = &o
|
||||
}
|
||||
return f.Write(file)
|
||||
}
|
||||
|
||||
|
@ -118,5 +121,18 @@ func (f *File) WriteToBuffer() (*bytes.Buffer, error) {
|
|||
return buf, err
|
||||
}
|
||||
}
|
||||
|
||||
if f.options != nil {
|
||||
if err := zw.Close(); err != nil {
|
||||
return buf, err
|
||||
}
|
||||
b, err := Encrypt(buf.Bytes(), f.options)
|
||||
if err != nil {
|
||||
return buf, err
|
||||
}
|
||||
buf.Reset()
|
||||
buf.Write(b)
|
||||
return buf, nil
|
||||
}
|
||||
return buf, zw.Close()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue