Merge pull request #217 from dennwc/refactor

Refactor Crypt Filter Methods (CFM)
This commit is contained in:
Gunnsteinn Hall 2018-09-25 23:22:26 +00:00 committed by GitHub
commit 6ff5fa3c02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 358 additions and 208 deletions

View File

@ -81,10 +81,22 @@ const padding = "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF" +
const StandardCryptFilter = "StdCF"
// CryptFilter represents information from a CryptFilter dictionary.
// TODO (v3): Unexport.
// TODO (v3): Replace with cryptFilterMethod interface.
type CryptFilter struct {
Cfm string // TODO (v3): CryptFilterMethod
Cfm string
Length int
cfm cryptFilterMethod
}
func (cf CryptFilter) getCFM() (cryptFilterMethod, error) {
// TODO (v3): remove this method and access cf.cfm directly
if cf.cfm != nil {
return cf.cfm, nil
}
// There is a non-zero chance that someone relies on the ability to
// add crypt filters manually using the library.
// So if we hit such case - be nice and find a filter by name.
return getCryptFilterMethod(cf.Cfm)
}
// Encryption filters names.
@ -96,10 +108,61 @@ const (
CryptFilterAESV3 = "AESV3" // AES-based filter (256 bit key, PDF 2.0)
)
func newCryptFiltersV2(length int) CryptFilters {
return CryptFilters{
StandardCryptFilter: NewCryptFilterV2(length),
}
}
// NewCryptFilterV2 creates a RC4-based filter with a specified key length (in bytes).
func NewCryptFilterV2(length int) CryptFilter {
// TODO (v3): Unexport.
return CryptFilter{
Cfm: CryptFilterV2,
Length: length,
cfm: cryptFilterV2{},
}
}
// NewCryptFilterAESV2 creates an AES-based filter with a 128 bit key (AESV2).
func NewCryptFilterAESV2() CryptFilter {
// TODO (v3): Unexport.
return CryptFilter{
Cfm: CryptFilterAESV2,
Length: 16,
cfm: cryptFilterAESV2{},
}
}
// NewCryptFilterAESV3 creates an AES-based filter with a 256 bit key (AESV3).
func NewCryptFilterAESV3() CryptFilter {
// TODO (v3): Unexport.
return CryptFilter{
Cfm: CryptFilterAESV3,
Length: 32,
cfm: cryptFilterAESV3{},
}
}
// CryptFilters is a map of crypt filter name and underlying CryptFilter info.
// TODO (v3): Unexport.
type CryptFilters map[string]CryptFilter
func (m CryptFilters) byName(cfm string) (cryptFilterMethod, error) {
cf, ok := m[cfm]
if !ok {
err := fmt.Errorf("Unsupported crypt filter (%s)", cfm)
common.Log.Debug("%s", err)
return nil, err
}
f, err := cf.getCFM()
if err != nil {
common.Log.Debug("%s", err)
return nil, err
}
return f, nil
}
// LoadCryptFilters loads crypt filter information from the encryption dictionary (V>=4).
// TODO (v3): Unexport.
func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
@ -154,25 +217,23 @@ func (crypt *PdfCrypt) LoadCryptFilters(ed *PdfObjectDictionary) error {
cf := CryptFilter{}
// Method.
cfMethod := ""
cfm, ok := dict.Get("CFM").(*PdfObjectName)
cfmName, ok := dict.Get("CFM").(*PdfObjectName)
if !ok {
return fmt.Errorf("Unsupported crypt filter (None)")
}
switch f := string(*cfm); f {
case CryptFilterV2,
CryptFilterAESV2,
CryptFilterAESV3:
cfMethod = f
default:
return fmt.Errorf("Unsupported crypt filter (%s)", f)
cf.Cfm = string(*cfmName)
cfm, err := getCryptFilterMethod(cf.Cfm)
if err != nil {
return err
}
cf.Cfm = cfMethod
cf.cfm = cfm
// Length.
cf.Length = 0
length, ok := dict.Get("Length").(*PdfObjectInteger)
if ok {
// TODO(dennwc): pass length to getCryptFilterMethod and allow filter to validate it
if *length%8 != 0 {
return fmt.Errorf("Crypt filter length not multiple of 8 (%d)", *length)
}
@ -284,8 +345,7 @@ func PdfCryptMakeNew(parser *PdfParser, ed, trailer *PdfObjectDictionary) (PdfCr
crypter.V = V
if V >= 1 && V <= 2 {
// Default algorithm is V2.
crypter.CryptFilters = CryptFilters{}
crypter.CryptFilters[StandardCryptFilter] = CryptFilter{Cfm: "V2", Length: crypter.Length}
crypter.CryptFilters = newCryptFiltersV2(crypter.Length)
} else if V >= 4 && V <= 5 {
if err := crypter.LoadCryptFilters(ed); err != nil {
return crypter, err
@ -589,48 +649,11 @@ func (crypt *PdfCrypt) paddedPass(pass []byte) []byte {
// Generates a key for encrypting a specific object based on the
// object and generation number, as well as the document encryption key.
func (crypt *PdfCrypt) makeKey(filter string, objNum, genNum uint32, ekey []byte) ([]byte, error) {
cf, ok := crypt.CryptFilters[filter]
if !ok {
common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter)
return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter)
f, err := crypt.CryptFilters.byName(filter)
if err != nil {
return nil, err
}
if cf.Cfm == CryptFilterAESV3 {
return ekey, nil
}
isAES2 := cf.Cfm == CryptFilterAESV2
key := make([]byte, len(ekey)+5)
for i := 0; i < len(ekey); i++ {
key[i] = ekey[i]
}
for i := 0; i < 3; i++ {
b := byte((objNum >> uint32(8*i)) & 0xff)
key[i+len(ekey)] = b
}
for i := 0; i < 2; i++ {
b := byte((genNum >> uint32(8*i)) & 0xff)
key[i+len(ekey)+3] = b
}
if isAES2 {
// If using the AES algorithm, extend the encryption key an
// additional 4 bytes by adding the value “sAlT”, which
// corresponds to the hexadecimal values 0x73, 0x41, 0x6C, 0x54.
key = append(key, 0x73)
key = append(key, 0x41)
key = append(key, 0x6C)
key = append(key, 0x54)
}
// Take the MD5.
h := md5.New()
h.Write(key)
hashb := h.Sum(nil)
if len(ekey)+5 < 16 {
return hashb[0 : len(ekey)+5], nil
}
return hashb, nil
return f.MakeKey(objNum, genNum, ekey)
}
// Check if object has already been processed.
@ -648,85 +671,11 @@ func (crypt *PdfCrypt) isDecrypted(obj PdfObject) bool {
// Decrypt a buffer with a selected crypt filter.
func (crypt *PdfCrypt) decryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) {
common.Log.Trace("Decrypt bytes")
cf, ok := crypt.CryptFilters[filter]
if !ok {
common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter)
return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter)
f, err := crypt.CryptFilters.byName(filter)
if err != nil {
return nil, err
}
cfMethod := cf.Cfm
if cfMethod == CryptFilterV2 {
// Standard RC4 algorithm.
ciph, err := rc4.NewCipher(okey)
if err != nil {
return nil, err
}
common.Log.Trace("RC4 Decrypt: % x", buf)
ciph.XORKeyStream(buf, buf)
common.Log.Trace("to: % x", buf)
return buf, nil
} else if cfMethod == CryptFilterAESV2 || cfMethod == CryptFilterAESV3 {
// Strings and streams encrypted with AES shall use a padding
// scheme that is described in Internet RFC 2898, PKCS #5:
// Password-Based Cryptography Specification Version 2.0; see
// the Bibliography. For an original message length of M,
// the pad shall consist of 16 - (M mod 16) bytes whose value
// shall also be 16 - (M mod 16).
//
// A 9-byte message has a pad of 7 bytes, each with the value
// 0x07. The pad can be unambiguously removed to determine the
// original message length when decrypting. Note that the pad is
// present when M is evenly divisible by 16; it contains 16 bytes
// of 0x10.
ciph, err := aes.NewCipher(okey)
if err != nil {
return nil, err
}
// If using the AES algorithm, the Cipher Block Chaining (CBC)
// mode, which requires an initialization vector, is used. The
// block size parameter is set to 16 bytes, and the initialization
// vector is a 16-byte random number that is stored as the first
// 16 bytes of the encrypted stream or string.
if len(buf) < 16 {
common.Log.Debug("ERROR AES invalid buf %s", buf)
return buf, fmt.Errorf("AES: Buf len < 16 (%d)", len(buf))
}
iv := buf[:16]
buf = buf[16:]
if len(buf)%16 != 0 {
common.Log.Debug(" iv (%d): % x", len(iv), iv)
common.Log.Debug("buf (%d): % x", len(buf), buf)
return buf, fmt.Errorf("AES buf length not multiple of 16 (%d)", len(buf))
}
mode := cipher.NewCBCDecrypter(ciph, iv)
common.Log.Trace("AES Decrypt (%d): % x", len(buf), buf)
common.Log.Trace("chop AES Decrypt (%d): % x", len(buf), buf)
mode.CryptBlocks(buf, buf)
common.Log.Trace("to (%d): % x", len(buf), buf)
if len(buf) == 0 {
common.Log.Trace("Empty buf, returning empty string")
return buf, nil
}
// The padded length is indicated by the last values. Remove those.
padLen := int(buf[len(buf)-1])
if padLen >= len(buf) {
common.Log.Debug("Illegal pad length")
return buf, fmt.Errorf("Invalid pad length for %s", cfMethod)
}
buf = buf[:len(buf)-padLen]
return buf, nil
}
return nil, fmt.Errorf("Unsupported crypt filter method (%s)", cfMethod)
return f.DecryptBytes(buf, okey)
}
// Decrypt an object with specified key. For numbered objects,
@ -906,74 +855,11 @@ func (crypt *PdfCrypt) isEncrypted(obj PdfObject) bool {
// Encrypt a buffer with the specified crypt filter and key.
func (crypt *PdfCrypt) encryptBytes(buf []byte, filter string, okey []byte) ([]byte, error) {
common.Log.Trace("Encrypt bytes")
cf, ok := crypt.CryptFilters[filter]
if !ok {
common.Log.Debug("ERROR Unsupported crypt filter (%s)", filter)
return nil, fmt.Errorf("Unsupported crypt filter (%s)", filter)
f, err := crypt.CryptFilters.byName(filter)
if err != nil {
return nil, err
}
cfMethod := cf.Cfm
if cfMethod == CryptFilterV2 {
// Standard RC4 algorithm.
ciph, err := rc4.NewCipher(okey)
if err != nil {
return nil, err
}
common.Log.Trace("RC4 Encrypt: % x", buf)
ciph.XORKeyStream(buf, buf)
common.Log.Trace("to: % x", buf)
return buf, nil
} else if cfMethod == CryptFilterAESV2 || cfMethod == CryptFilterAESV3 {
// Strings and streams encrypted with AES shall use a padding
// scheme that is described in Internet RFC 2898, PKCS #5:
// Password-Based Cryptography Specification Version 2.0; see
// the Bibliography. For an original message length of M,
// the pad shall consist of 16 - (M mod 16) bytes whose value
// shall also be 16 - (M mod 16).
//
// A 9-byte message has a pad of 7 bytes, each with the value
// 0x07. The pad can be unambiguously removed to determine the
// original message length when decrypting. Note that the pad is
// present when M is evenly divisible by 16; it contains 16 bytes
// of 0x10.
ciph, err := aes.NewCipher(okey)
if err != nil {
return nil, err
}
common.Log.Trace("AES Encrypt (%d): % x", len(buf), buf)
// If using the AES algorithm, the Cipher Block Chaining (CBC)
// mode, which requires an initialization vector, is used. The
// block size parameter is set to 16 bytes, and the initialization
// vector is a 16-byte random number that is stored as the first
// 16 bytes of the encrypted stream or string.
const block = aes.BlockSize // 16
pad := block - len(buf)%block
for i := 0; i < pad; i++ {
buf = append(buf, byte(pad))
}
common.Log.Trace("Padded to %d bytes", len(buf))
// Generate random 16 bytes, place in beginning of buffer.
ciphertext := make([]byte, block+len(buf))
iv := ciphertext[:block]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(ciph, iv)
mode.CryptBlocks(ciphertext[block:], buf)
buf = ciphertext
common.Log.Trace("to (%d): % x", len(buf), buf)
return buf, nil
}
return nil, fmt.Errorf("Unsupported crypt filter method (%s)", cfMethod)
return f.EncryptBytes(buf, okey)
}
// Encrypt an object with specified key. For numbered objects,

266
pdf/core/crypt_filters.go Normal file
View File

@ -0,0 +1,266 @@
package core
import (
"crypto/aes"
"crypto/cipher"
"crypto/md5"
"crypto/rand"
"crypto/rc4"
"fmt"
"io"
"github.com/unidoc/unidoc/common"
)
var (
cryptMethods = make(map[string]cryptFilterMethod)
)
// registerCryptFilterMethod registers a CFM.
func registerCryptFilterMethod(m cryptFilterMethod) {
cryptMethods[m.CFM()] = m
}
// getCryptFilterMethod check if a CFM with a specified name is supported an returns its implementation.
func getCryptFilterMethod(name string) (cryptFilterMethod, error) {
f := cryptMethods[name]
if f == nil {
return nil, fmt.Errorf("unsupported crypt filter: %q", name)
}
return f, nil
}
func init() {
// register supported crypt filter methods
registerCryptFilterMethod(cryptFilterV2{})
registerCryptFilterMethod(cryptFilterAESV2{})
registerCryptFilterMethod(cryptFilterAESV3{})
}
// cryptFilterMethod is a common interface for crypt filter methods.
type cryptFilterMethod interface {
// CFM returns a name of the filter that should be used in CFM field of Encrypt dictionary.
CFM() string
// MakeKey generates a object encryption key based on file encryption key and object numbers.
// Used only for legacy filters - AESV3 doesn't change the key for each object.
MakeKey(objNum, genNum uint32, fkey []byte) ([]byte, error)
// EncryptBytes encrypts a buffer using object encryption key, as returned by MakeKey.
// Implementation may reuse a buffer and encrypt data in-place.
EncryptBytes(p []byte, okey []byte) ([]byte, error)
// DecryptBytes decrypts a buffer using object encryption key, as returned by MakeKey.
// Implementation may reuse a buffer and decrypt data in-place.
DecryptBytes(p []byte, okey []byte) ([]byte, error)
}
// makeKeyV2 is a common object key generation shared by V2 and AESV2 crypt filters.
func makeKeyV2(objNum, genNum uint32, ekey []byte, isAES bool) ([]byte, error) {
key := make([]byte, len(ekey)+5)
for i := 0; i < len(ekey); i++ {
key[i] = ekey[i]
}
for i := 0; i < 3; i++ {
b := byte((objNum >> uint32(8*i)) & 0xff)
key[i+len(ekey)] = b
}
for i := 0; i < 2; i++ {
b := byte((genNum >> uint32(8*i)) & 0xff)
key[i+len(ekey)+3] = b
}
if isAES {
// If using the AES algorithm, extend the encryption key an
// additional 4 bytes by adding the value “sAlT”, which
// corresponds to the hexadecimal values 0x73, 0x41, 0x6C, 0x54.
key = append(key, 0x73)
key = append(key, 0x41)
key = append(key, 0x6C)
key = append(key, 0x54)
}
// Take the MD5.
h := md5.New()
h.Write(key)
hashb := h.Sum(nil)
if len(ekey)+5 < 16 {
return hashb[0 : len(ekey)+5], nil
}
return hashb, nil
}
// cryptFilterV2 is a RC4-based filter
type cryptFilterV2 struct{}
func (cryptFilterV2) CFM() string {
return CryptFilterV2
}
func (f cryptFilterV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) {
return makeKeyV2(objNum, genNum, ekey, false)
}
func (cryptFilterV2) EncryptBytes(buf []byte, okey []byte) ([]byte, error) {
// Standard RC4 algorithm.
ciph, err := rc4.NewCipher(okey)
if err != nil {
return nil, err
}
common.Log.Trace("RC4 Encrypt: % x", buf)
ciph.XORKeyStream(buf, buf)
common.Log.Trace("to: % x", buf)
return buf, nil
}
func (cryptFilterV2) DecryptBytes(buf []byte, okey []byte) ([]byte, error) {
// Standard RC4 algorithm.
ciph, err := rc4.NewCipher(okey)
if err != nil {
return nil, err
}
common.Log.Trace("RC4 Decrypt: % x", buf)
ciph.XORKeyStream(buf, buf)
common.Log.Trace("to: % x", buf)
return buf, nil
}
// cryptFilterAES implements a generic AES encryption and decryption algorithm used by AESV2 and AESV3 filter methods.
type cryptFilterAES struct{}
func (cryptFilterAES) EncryptBytes(buf []byte, okey []byte) ([]byte, error) {
// Strings and streams encrypted with AES shall use a padding
// scheme that is described in Internet RFC 2898, PKCS #5:
// Password-Based Cryptography Specification Version 2.0; see
// the Bibliography. For an original message length of M,
// the pad shall consist of 16 - (M mod 16) bytes whose value
// shall also be 16 - (M mod 16).
//
// A 9-byte message has a pad of 7 bytes, each with the value
// 0x07. The pad can be unambiguously removed to determine the
// original message length when decrypting. Note that the pad is
// present when M is evenly divisible by 16; it contains 16 bytes
// of 0x10.
ciph, err := aes.NewCipher(okey)
if err != nil {
return nil, err
}
common.Log.Trace("AES Encrypt (%d): % x", len(buf), buf)
// If using the AES algorithm, the Cipher Block Chaining (CBC)
// mode, which requires an initialization vector, is used. The
// block size parameter is set to 16 bytes, and the initialization
// vector is a 16-byte random number that is stored as the first
// 16 bytes of the encrypted stream or string.
const block = aes.BlockSize // 16
pad := block - len(buf)%block
for i := 0; i < pad; i++ {
buf = append(buf, byte(pad))
}
common.Log.Trace("Padded to %d bytes", len(buf))
// Generate random 16 bytes, place in beginning of buffer.
ciphertext := make([]byte, block+len(buf))
iv := ciphertext[:block]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
mode := cipher.NewCBCEncrypter(ciph, iv)
mode.CryptBlocks(ciphertext[block:], buf)
buf = ciphertext
common.Log.Trace("to (%d): % x", len(buf), buf)
return buf, nil
}
func (cryptFilterAES) DecryptBytes(buf []byte, okey []byte) ([]byte, error) {
// Strings and streams encrypted with AES shall use a padding
// scheme that is described in Internet RFC 2898, PKCS #5:
// Password-Based Cryptography Specification Version 2.0; see
// the Bibliography. For an original message length of M,
// the pad shall consist of 16 - (M mod 16) bytes whose value
// shall also be 16 - (M mod 16).
//
// A 9-byte message has a pad of 7 bytes, each with the value
// 0x07. The pad can be unambiguously removed to determine the
// original message length when decrypting. Note that the pad is
// present when M is evenly divisible by 16; it contains 16 bytes
// of 0x10.
ciph, err := aes.NewCipher(okey)
if err != nil {
return nil, err
}
// If using the AES algorithm, the Cipher Block Chaining (CBC)
// mode, which requires an initialization vector, is used. The
// block size parameter is set to 16 bytes, and the initialization
// vector is a 16-byte random number that is stored as the first
// 16 bytes of the encrypted stream or string.
if len(buf) < 16 {
common.Log.Debug("ERROR AES invalid buf %s", buf)
return buf, fmt.Errorf("AES: Buf len < 16 (%d)", len(buf))
}
iv := buf[:16]
buf = buf[16:]
if len(buf)%16 != 0 {
common.Log.Debug(" iv (%d): % x", len(iv), iv)
common.Log.Debug("buf (%d): % x", len(buf), buf)
return buf, fmt.Errorf("AES buf length not multiple of 16 (%d)", len(buf))
}
mode := cipher.NewCBCDecrypter(ciph, iv)
common.Log.Trace("AES Decrypt (%d): % x", len(buf), buf)
common.Log.Trace("chop AES Decrypt (%d): % x", len(buf), buf)
mode.CryptBlocks(buf, buf)
common.Log.Trace("to (%d): % x", len(buf), buf)
if len(buf) == 0 {
common.Log.Trace("Empty buf, returning empty string")
return buf, nil
}
// The padded length is indicated by the last values. Remove those.
padLen := int(buf[len(buf)-1])
if padLen >= len(buf) {
common.Log.Debug("Illegal pad length")
return buf, fmt.Errorf("Invalid pad length")
}
buf = buf[:len(buf)-padLen]
return buf, nil
}
// cryptFilterAESV2 is an AES-based filter (128 bit key, PDF 1.6)
type cryptFilterAESV2 struct {
cryptFilterAES
}
func (cryptFilterAESV2) CFM() string {
return CryptFilterAESV2
}
func (cryptFilterAESV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) {
return makeKeyV2(objNum, genNum, ekey, true)
}
// cryptFilterAESV3 is an AES-based filter (256 bit key, PDF 2.0)
type cryptFilterAESV3 struct {
cryptFilterAES
}
func (cryptFilterAESV3) CFM() string {
return CryptFilterAESV3
}
func (cryptFilterAESV3) MakeKey(_, _ uint32, ekey []byte) ([]byte, error) {
return ekey, nil
}

View File

@ -148,8 +148,7 @@ func TestDecryption1(t *testing.T) {
crypter := PdfCrypt{}
crypter.DecryptedObjects = map[PdfObject]bool{}
// Default algorithm is V2 (RC4).
crypter.CryptFilters = CryptFilters{}
crypter.CryptFilters[StandardCryptFilter] = CryptFilter{Cfm: "V2", Length: crypter.Length}
crypter.CryptFilters = newCryptFiltersV2(crypter.Length)
crypter.V = 2
crypter.R = 3
crypter.P = -3904

View File

@ -509,23 +509,22 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
case RC4_128bit:
crypter.V = 2
crypter.R = 3
crypter.Length = 128
cf = CryptFilter{Cfm: CryptFilterV2, Length: 16}
cf = NewCryptFilterV2(16)
case AES_128bit:
this.SetVersion(1, 5)
crypter.V = 4
crypter.R = 4
crypter.Length = 128
cf = CryptFilter{Cfm: CryptFilterAESV2, Length: 16}
cf = NewCryptFilterAESV2()
case AES_256bit:
this.SetVersion(2, 0)
crypter.V = 5
crypter.R = 6 // TODO(dennwc): a way to set R=5?
crypter.Length = 256
cf = CryptFilter{Cfm: CryptFilterAESV3, Length: 32}
cf = NewCryptFilterAESV3()
default:
return fmt.Errorf("unsupported algorithm: %v", options.Algorithm)
}
crypter.Length = cf.Length * 8
const (
defaultFilter = StandardCryptFilter
)