core: split crypt filter methods into a separate package

This commit is contained in:
Denys Smirnov 2018-10-04 05:35:00 +03:00
parent 42df346e69
commit 7bd4ba688d
8 changed files with 560 additions and 489 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/pdf/core/security"
crypto "github.com/unidoc/unidoc/pdf/core/security/crypt"
)
type Version struct {
@ -28,7 +29,7 @@ type EncryptInfo struct {
}
// PdfCryptNewEncrypt makes the document crypt handler based on a specified crypt filter.
func PdfCryptNewEncrypt(cf CryptFilter, userPass, ownerPass []byte, perm security.Permissions) (*PdfCrypt, *EncryptInfo, error) {
func PdfCryptNewEncrypt(cf crypto.Filter, userPass, ownerPass []byte, perm security.Permissions) (*PdfCrypt, *EncryptInfo, error) {
crypter := &PdfCrypt{
encryptedObjects: make(map[PdfObject]bool),
cryptFilters: make(cryptFilters),
@ -37,26 +38,19 @@ func PdfCryptNewEncrypt(cf CryptFilter, userPass, ownerPass []byte, perm securit
EncryptMetadata: true,
},
}
// TODO(dennwc): define it in the CF interface
var vers Version
switch cf.(type) {
case cryptFilterV2:
crypter.encrypt.V = 2
crypter.encryptStd.R = 3
case cryptFilterAESV2:
vers.Major, vers.Minor = 1, 5
crypter.encrypt.V = 4
crypter.encryptStd.R = 4
case cryptFilterAESV3:
vers.Major, vers.Minor = 2, 0
crypter.encrypt.V = 5
crypter.encryptStd.R = 6
}
if cf != nil {
v := cf.PDFVersion()
vers.Major, vers.Minor = v[0], v[1]
V, R := cf.HandlerVersion()
crypter.encrypt.V = V
crypter.encryptStd.R = R
crypter.encrypt.Length = cf.KeyLength() * 8
}
const (
defaultFilter = StandardCryptFilter
defaultFilter = stdCryptFilter
)
crypter.cryptFilters[defaultFilter] = cf
if crypter.encrypt.V >= 4 {
@ -97,6 +91,28 @@ func PdfCryptNewEncrypt(cf CryptFilter, userPass, ownerPass []byte, perm securit
}, nil
}
// PdfCrypt provides PDF encryption/decryption support.
// The PDF standard supports encryption of strings and streams (Section 7.6).
// TODO (v3): Consider unexporting.
type PdfCrypt struct {
encrypt encryptDict
encryptStd security.StdEncryptDict
id0 string
encryptionKey []byte
decryptedObjects map[PdfObject]bool
encryptedObjects map[PdfObject]bool
authenticated bool
// Crypt filters (V4).
cryptFilters cryptFilters
streamFilter string
stringFilter string
parser *PdfParser
decryptedObjNum map[int]struct{}
}
// encodeEncryptStd encodes fields of standard security handler to an Encrypt dictionary.
func encodeEncryptStd(d *security.StdEncryptDict, ed *PdfObjectDictionary) {
ed.Set("R", MakeInteger(int64(d.R)))
@ -200,26 +216,32 @@ func decodeEncryptStd(d *security.StdEncryptDict, ed *PdfObjectDictionary) error
return nil
}
// PdfCrypt provides PDF encryption/decryption support.
// The PDF standard supports encryption of strings and streams (Section 7.6).
// TODO (v3): Consider unexporting.
type PdfCrypt struct {
encrypt encryptDict
encryptStd security.StdEncryptDict
func decodeCryptFilter(cf *crypto.FilterDict, d *PdfObjectDictionary) error {
// If Type present, should be CryptFilter.
if typename, ok := d.Get("Type").(*PdfObjectName); ok {
if string(*typename) != "CryptFilter" {
return fmt.Errorf("CF dict type != CryptFilter (%s)", typename)
}
}
id0 string
encryptionKey []byte
decryptedObjects map[PdfObject]bool
encryptedObjects map[PdfObject]bool
authenticated bool
// Crypt filters (V4).
cryptFilters cryptFilters
streamFilter string
stringFilter string
// Method.
name, ok := d.Get("CFM").(*PdfObjectName)
if !ok {
return fmt.Errorf("Unsupported crypt filter (None)")
}
cf.CFM = string(*name)
parser *PdfParser
// Auth event
if event, ok := d.Get("AuthEvent").(*PdfObjectName); ok {
cf.AuthEvent = security.AuthEvent(*event)
} else {
cf.AuthEvent = security.EventDocOpen
}
decryptedObjNum map[int]struct{}
if length, ok := d.Get("Length").(*PdfObjectInteger); ok {
cf.Length = int(*length)
}
return nil
}
func (crypt *PdfCrypt) newEncyptDict() *PdfObjectDictionary {
@ -262,65 +284,31 @@ func (crypt *PdfCrypt) String() string {
return str
}
type authEvent string
const (
authEventDocOpen = authEvent("DocOpen")
authEventEFOpen = authEvent("EFOpen")
)
type cryptFiltersDict map[string]cryptFilterDict
// encryptDict is a set of field common to all encryption dictionaries.
type encryptDict struct {
Filter string // (Required) The name of the preferred security handler for this document.
V int // (Required) A code specifying the algorithm to be used in encrypting and decrypting the document.
SubFilter string // Completely specifies the format and interpretation of the encryption dictionary.
Length int // The length of the encryption key, in bits.
CF cryptFiltersDict // Crypt filters dictionary.
StmF string // The filter that shall be used by default when decrypting streams.
StrF string // The filter that shall be used when decrypting all strings in the document.
EFF string // The filter that shall be used when decrypting embedded file streams.
Filter string // (Required) The name of the preferred security handler for this document.
V int // (Required) A code specifying the algorithm to be used in encrypting and decrypting the document.
SubFilter string // Completely specifies the format and interpretation of the encryption dictionary.
Length int // The length of the encryption key, in bits.
StmF string // The filter that shall be used by default when decrypting streams.
StrF string // The filter that shall be used when decrypting all strings in the document.
EFF string // The filter that shall be used when decrypting embedded file streams.
CF map[string]crypto.FilterDict // Crypt filters dictionary.
}
// StandardCryptFilter is a default name for a standard crypt filter.
const StandardCryptFilter = "StdCF"
// stdCryptFilter is a default name for a standard crypt filter.
const stdCryptFilter = "StdCF"
func newCryptFiltersV2(length int) cryptFilters {
return cryptFilters{
StandardCryptFilter: NewCryptFilterV2(length),
stdCryptFilter: crypto.NewFilterV2(length),
}
}
// NewCryptFilterV2 creates a RC4-based filter with a specified key length (in bytes).
func NewCryptFilterV2(length int) CryptFilter {
f, err := newCryptFilterV2(cryptFilterDict{Length: length})
if err != nil {
panic(err)
}
return f
}
// NewCryptFilterAESV2 creates an AES-based filter with a 128 bit key (AESV2).
func NewCryptFilterAESV2() CryptFilter {
f, err := newCryptFilterAESV2(cryptFilterDict{})
if err != nil {
panic(err)
}
return f
}
// NewCryptFilterAESV3 creates an AES-based filter with a 256 bit key (AESV3).
func NewCryptFilterAESV3() CryptFilter {
f, err := newCryptFilterAESV3(cryptFilterDict{})
if err != nil {
panic(err)
}
return f
}
// cryptFilters is a map of crypt filter name and underlying CryptFilter info.
type cryptFilters map[string]CryptFilter
type cryptFilters map[string]crypto.Filter
// loadCryptFilters loads crypt filter information from the encryption dictionary (V>=4).
func (crypt *PdfCrypt) loadCryptFilters(ed *PdfObjectDictionary) error {
@ -365,22 +353,18 @@ func (crypt *PdfCrypt) loadCryptFilters(ed *PdfObjectDictionary) error {
continue
}
var cfd cryptFilterDict
if err := cfd.ReadFrom(dict); err != nil {
var cfd crypto.FilterDict
if err := decodeCryptFilter(&cfd, dict); err != nil {
return err
}
fnc, err := getCryptFilterMethod(cfd.CFM)
if err != nil {
return err
}
cf, err := fnc(cfd)
cf, err := crypto.NewFilter(cfd)
if err != nil {
return err
}
crypt.cryptFilters[string(name)] = cf
}
// Cannot be overwritten.
crypt.cryptFilters["Identity"] = cryptFilteridentity{}
crypt.cryptFilters["Identity"] = crypto.NewIdentity()
// StrF strings filter.
crypt.stringFilter = "Identity"
@ -403,6 +387,18 @@ func (crypt *PdfCrypt) loadCryptFilters(ed *PdfObjectDictionary) error {
return nil
}
func encodeCryptFilter(cf crypto.Filter, event security.AuthEvent) *PdfObjectDictionary {
if event == "" {
event = security.EventDocOpen
}
v := MakeDict()
v.Set("Type", MakeName("CryptFilter")) // optional
v.Set("AuthEvent", MakeName(string(event)))
v.Set("CFM", MakeName(cf.Name()))
v.Set("Length", MakeInteger(int64(cf.KeyLength())))
return v
}
// saveCryptFilters saves crypt filter information to the encryption dictionary (V>=4).
func (crypt *PdfCrypt) saveCryptFilters(ed *PdfObjectDictionary) error {
if crypt.encrypt.V < 4 {
@ -415,7 +411,7 @@ func (crypt *PdfCrypt) saveCryptFilters(ed *PdfObjectDictionary) error {
if name == "Identity" {
continue
}
v := cryptFilterToDict(filter, "")
v := encodeCryptFilter(filter, "")
cf.Set(PdfObjectName(name), v)
}
ed.Set("StrF", MakeName(crypt.stringFilter))
@ -654,7 +650,7 @@ func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64)
// TODO: Check for crypt filter (V4).
// The Crypt filter shall be the first filter in the Filter array entry.
streamFilter := StandardCryptFilter // Default RC4.
streamFilter := stdCryptFilter // Default RC4.
if crypt.encrypt.V >= 4 {
streamFilter = crypt.streamFilter
common.Log.Trace("this.streamFilter = %s", crypt.streamFilter)
@ -708,7 +704,7 @@ func (crypt *PdfCrypt) Decrypt(obj PdfObject, parentObjNum, parentGenNum int64)
case *PdfObjectString:
common.Log.Trace("Decrypting string!")
stringFilter := StandardCryptFilter
stringFilter := stdCryptFilter
if crypt.encrypt.V >= 4 {
// Currently only support Identity / RC4.
common.Log.Trace("with %s filter", crypt.stringFilter)
@ -837,7 +833,7 @@ func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64)
// TODO: Check for crypt filter (V4).
// The Crypt filter shall be the first filter in the Filter array entry.
streamFilter := StandardCryptFilter // Default RC4.
streamFilter := stdCryptFilter // Default RC4.
if crypt.encrypt.V >= 4 {
// For now. Need to change when we add support for more than
// Identity / RC4.
@ -893,7 +889,7 @@ func (crypt *PdfCrypt) Encrypt(obj PdfObject, parentObjNum, parentGenNum int64)
case *PdfObjectString:
common.Log.Trace("Encrypting string!")
stringFilter := StandardCryptFilter
stringFilter := stdCryptFilter
if crypt.encrypt.V >= 4 {
common.Log.Trace("with %s filter", crypt.stringFilter)
if crypt.stringFilter == "Identity" {

View File

@ -1,389 +0,0 @@
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]cryptFilterFunc)
)
// cryptFilterDict represents information from a CryptFilter dictionary.
type cryptFilterDict struct {
CFM string // The method used, if any, by the PDF reader to decrypt data.
AuthEvent authEvent
Length int // in bytes
}
func (cf *cryptFilterDict) ReadFrom(d *PdfObjectDictionary) error {
// If Type present, should be CryptFilter.
if typename, ok := d.Get("Type").(*PdfObjectName); ok {
if string(*typename) != "CryptFilter" {
return fmt.Errorf("CF dict type != CryptFilter (%s)", typename)
}
}
// Method.
name, ok := d.Get("CFM").(*PdfObjectName)
if !ok {
return fmt.Errorf("Unsupported crypt filter (None)")
}
cf.CFM = string(*name)
// Auth event
if event, ok := d.Get("AuthEvent").(*PdfObjectName); ok {
cf.AuthEvent = authEvent(*event)
} else {
cf.AuthEvent = authEventDocOpen
}
if length, ok := d.Get("Length").(*PdfObjectInteger); ok {
cf.Length = int(*length)
}
return nil
}
// cryptFilterFunc is used to construct crypt filters from CryptFilter dictionary
type cryptFilterFunc func(d cryptFilterDict) (CryptFilter, error)
// registerCryptFilterMethod registers a CFM.
func registerCryptFilterMethod(name string, fnc cryptFilterFunc) {
if _, ok := cryptMethods[name]; ok {
panic("already registered")
}
cryptMethods[name] = fnc
}
// getCryptFilterMethod check if a CFM with a specified name is supported an returns its implementation.
func getCryptFilterMethod(name string) (cryptFilterFunc, error) {
f := cryptMethods[string(name)]
if f == nil {
return nil, fmt.Errorf("unsupported crypt filter: %q", name)
}
return f, nil
}
func init() {
// Register supported crypt filter methods.
// Table 25, CFM (page 92)
registerCryptFilterMethod("V2", newCryptFilterV2)
registerCryptFilterMethod("AESV2", newCryptFilterAESV2)
registerCryptFilterMethod("AESV3", newCryptFilterAESV3)
}
// CryptFilter is a common interface for crypt filter methods.
type CryptFilter interface {
// Name returns a name of the filter that should be used in CFM field of Encrypt dictionary.
Name() string
// KeyLength returns a length of the encryption key in bytes.
KeyLength() int
// 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)
}
func cryptFilterToDict(cf CryptFilter, event authEvent) *PdfObjectDictionary {
if event == "" {
event = authEventDocOpen
}
v := MakeDict()
v.Set("Type", MakeName("CryptFilter")) // optional
v.Set("AuthEvent", MakeName(string(event)))
v.Set("CFM", MakeName(cf.Name()))
v.Set("Length", MakeInteger(int64(cf.KeyLength())))
return v
}
type cryptFilteridentity struct{}
func (cryptFilteridentity) Name() string {
return "Identity"
}
func (cryptFilteridentity) KeyLength() int {
return 0
}
func (cryptFilteridentity) MakeKey(objNum, genNum uint32, fkey []byte) ([]byte, error) {
return fkey, nil
}
func (cryptFilteridentity) EncryptBytes(p []byte, okey []byte) ([]byte, error) {
return p, nil
}
func (cryptFilteridentity) DecryptBytes(p []byte, okey []byte) ([]byte, error) {
return p, nil
}
// 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
}
func newCryptFilterV2(d cryptFilterDict) (CryptFilter, error) {
if d.Length%8 != 0 {
return nil, fmt.Errorf("Crypt filter length not multiple of 8 (%d)", d.Length)
}
// Standard security handler expresses the length in multiples of 8 (16 means 128)
// We only deal with standard so far. (Public key not supported yet).
if d.Length < 5 || d.Length > 16 {
if d.Length == 64 || d.Length == 128 {
common.Log.Debug("STANDARD VIOLATION: Crypt Length appears to be in bits rather than bytes - assuming bits (%d)", d.Length)
d.Length /= 8
} else {
return nil, fmt.Errorf("Crypt filter length not in range 40 - 128 bit (%d)", d.Length)
}
}
return cryptFilterV2{length: d.Length}, nil
}
// cryptFilterV2 is a RC4-based filter
type cryptFilterV2 struct {
length int
}
func (cryptFilterV2) Name() string {
return "V2"
}
func (f cryptFilterV2) KeyLength() int {
return f.length
}
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
}
func newCryptFilterAESV2(d cryptFilterDict) (CryptFilter, error) {
if d.Length != 0 && d.Length != 16 {
return nil, fmt.Errorf("Invalid AESV2 crypt filter length (%d)", d.Length)
}
return cryptFilterAESV2{}, nil
}
// cryptFilterAESV2 is an AES-based filter (128 bit key, PDF 1.6)
type cryptFilterAESV2 struct {
cryptFilterAES
}
func (cryptFilterAESV2) Name() string {
return "AESV2"
}
func (cryptFilterAESV2) KeyLength() int {
return 128 / 8
}
func (cryptFilterAESV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) {
return makeKeyV2(objNum, genNum, ekey, true)
}
func newCryptFilterAESV3(d cryptFilterDict) (CryptFilter, error) {
if d.Length != 0 && d.Length != 32 {
return nil, fmt.Errorf("Invalid AESV3 crypt filter length (%d)", d.Length)
}
return cryptFilterAESV3{}, nil
}
// cryptFilterAESV3 is an AES-based filter (256 bit key, PDF 2.0)
type cryptFilterAESV3 struct {
cryptFilterAES
}
func (cryptFilterAESV3) Name() string {
return "AESV3"
}
func (cryptFilterAESV3) KeyLength() int {
return 256 / 8
}
func (cryptFilterAESV3) MakeKey(_, _ uint32, ekey []byte) ([]byte, error) {
return ekey, nil
}

View File

@ -0,0 +1,9 @@
package security
// AuthEvent is an event type that triggers authentication.
type AuthEvent string
const (
EventDocOpen = AuthEvent("DocOpen") // document open
EventEFOpen = AuthEvent("EFOpen") // embedded file open
)

View File

@ -0,0 +1,49 @@
package crypt
import "fmt"
func init() {
registerFilter("AESV2", newFilterAESV2)
}
// NewFilterAESV2 creates an AES-based filter with a 128 bit key (AESV2).
func NewFilterAESV2() Filter {
f, err := newFilterAESV2(FilterDict{})
if err != nil {
panic(err)
}
return f
}
func newFilterAESV2(d FilterDict) (Filter, error) {
if d.Length != 0 && d.Length != 16 {
return nil, fmt.Errorf("Invalid AESV2 crypt filter length (%d)", d.Length)
}
return filterAESV2{}, nil
}
// filterAESV2 is an AES-based filter (128 bit key, PDF 1.6)
type filterAESV2 struct {
filterAES
}
func (filterAESV2) PDFVersion() [2]int {
return [2]int{1, 5}
}
func (filterAESV2) HandlerVersion() (V, R int) {
V, R = 4, 4
return
}
func (filterAESV2) Name() string {
return "AESV2"
}
func (filterAESV2) KeyLength() int {
return 128 / 8
}
func (filterAESV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) {
return makeKeyV2(objNum, genNum, ekey, true)
}

View File

@ -0,0 +1,173 @@
package crypt
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
"github.com/unidoc/unidoc/common"
)
func init() {
registerFilter("AESV3", newFilterAESV3)
}
// NewFilterAESV3 creates an AES-based filter with a 256 bit key (AESV3).
func NewFilterAESV3() Filter {
f, err := newFilterAESV3(FilterDict{})
if err != nil {
panic(err)
}
return f
}
func newFilterAESV3(d FilterDict) (Filter, error) {
if d.Length != 0 && d.Length != 32 {
return nil, fmt.Errorf("Invalid AESV3 crypt filter length (%d)", d.Length)
}
return filterAESV3{}, nil
}
// filterAES implements a generic AES encryption and decryption algorithm used by AESV2 and AESV3 filter methods.
type filterAES struct{}
func (filterAES) 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 (filterAES) 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
}
// filterAESV3 is an AES-based filter (256 bit key, PDF 2.0)
type filterAESV3 struct {
filterAES
}
func (filterAESV3) PDFVersion() [2]int {
return [2]int{2, 0}
}
func (filterAESV3) HandlerVersion() (V, R int) {
V, R = 5, 6
return
}
func (filterAESV3) Name() string {
return "AESV3"
}
func (filterAESV3) KeyLength() int {
return 256 / 8
}
func (filterAESV3) MakeKey(_, _ uint32, ekey []byte) ([]byte, error) {
return ekey, nil
}

View File

@ -0,0 +1,125 @@
package crypt
import (
"crypto/md5"
"crypto/rc4"
"fmt"
"github.com/unidoc/unidoc/common"
)
func init() {
registerFilter("V2", newFilterV2)
}
// NewFilterV2 creates a RC4-based filter with a specified key length (in bytes).
func NewFilterV2(length int) Filter {
f, err := newFilterV2(FilterDict{Length: length})
if err != nil {
panic(err)
}
return f
}
func newFilterV2(d FilterDict) (Filter, error) {
if d.Length%8 != 0 {
return nil, fmt.Errorf("Crypt filter length not multiple of 8 (%d)", d.Length)
}
// Standard security handler expresses the length in multiples of 8 (16 means 128)
// We only deal with standard so far. (Public key not supported yet).
if d.Length < 5 || d.Length > 16 {
if d.Length == 64 || d.Length == 128 {
common.Log.Debug("STANDARD VIOLATION: Crypt Length appears to be in bits rather than bytes - assuming bits (%d)", d.Length)
d.Length /= 8
} else {
return nil, fmt.Errorf("Crypt filter length not in range 40 - 128 bit (%d)", d.Length)
}
}
return filterV2{length: d.Length}, nil
}
// 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
}
// filterV2 is a RC4-based filter
type filterV2 struct {
length int
}
func (f filterV2) PDFVersion() [2]int {
return [2]int{} // TODO(dennwc): unspecified; check what it should be
}
func (f filterV2) HandlerVersion() (V, R int) {
V, R = 2, 3
return
}
func (filterV2) Name() string {
return "V2"
}
func (f filterV2) KeyLength() int {
return f.length
}
func (f filterV2) MakeKey(objNum, genNum uint32, ekey []byte) ([]byte, error) {
return makeKeyV2(objNum, genNum, ekey, false)
}
func (filterV2) 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 (filterV2) 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
}

View File

@ -0,0 +1,108 @@
package crypt
import (
"fmt"
"github.com/unidoc/unidoc/pdf/core/security"
)
var (
filterMethods = make(map[string]filterFunc)
)
// filterFunc is used to construct crypt filters from CryptFilter dictionary
type filterFunc func(d FilterDict) (Filter, error)
// NewFilter creates CryptFilter from a corresponding dictionary.
func NewFilter(d FilterDict) (Filter, error) {
fnc, err := getFilter(d.CFM)
if err != nil {
return nil, err
}
cf, err := fnc(d)
if err != nil {
return nil, err
}
return cf, nil
}
// NewIdentity creates an identity filter that bypasses all data without changes.
func NewIdentity() Filter {
return filterIdentity{}
}
// FilterDict represents information from a CryptFilter dictionary.
type FilterDict struct {
CFM string // The method used, if any, by the PDF reader to decrypt data.
AuthEvent security.AuthEvent
Length int // in bytes
}
// registerFilter register supported crypt filter methods.
// Table 25, CFM (page 92)
func registerFilter(name string, fnc filterFunc) {
if _, ok := filterMethods[name]; ok {
panic("already registered")
}
filterMethods[name] = fnc
}
// getFilter check if a CFM with a specified name is supported an returns its implementation.
func getFilter(name string) (filterFunc, error) {
f := filterMethods[string(name)]
if f == nil {
return nil, fmt.Errorf("unsupported crypt filter: %q", name)
}
return f, nil
}
// Filter is a common interface for crypt filter methods.
type Filter interface {
// Name returns a name of the filter that should be used in CFM field of Encrypt dictionary.
Name() string
// KeyLength returns a length of the encryption key in bytes.
KeyLength() int
// PDFVersion reports the minimal version of PDF document that introduced this filter.
PDFVersion() [2]int
// HandlerVersion reports V and R parameters that should be used for this filter.
HandlerVersion() (V, R int)
// 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)
}
type filterIdentity struct{}
func (filterIdentity) PDFVersion() [2]int {
return [2]int{}
}
func (filterIdentity) HandlerVersion() (V, R int) {
return
}
func (filterIdentity) Name() string {
return "Identity"
}
func (filterIdentity) KeyLength() int {
return 0
}
func (filterIdentity) MakeKey(objNum, genNum uint32, fkey []byte) ([]byte, error) {
return fkey, nil
}
func (filterIdentity) EncryptBytes(p []byte, okey []byte) ([]byte, error) {
return p, nil
}
func (filterIdentity) DecryptBytes(p []byte, okey []byte) ([]byte, error) {
return p, nil
}

View File

@ -17,6 +17,8 @@ import (
"io"
"strings"
"github.com/unidoc/unidoc/pdf/core/security/crypt"
"github.com/unidoc/unidoc/common"
"github.com/unidoc/unidoc/common/license"
. "github.com/unidoc/unidoc/pdf/core"
@ -676,16 +678,14 @@ func (this *PdfWriter) Encrypt(userPass, ownerPass []byte, options *EncryptOptio
perm = options.Permissions
}
var cf CryptFilter
var cf crypt.Filter
switch algo {
case RC4_128bit:
cf = NewCryptFilterV2(16)
cf = crypt.NewFilterV2(16)
case AES_128bit:
this.SetVersion(1, 5)
cf = NewCryptFilterAESV2()
cf = crypt.NewFilterAESV2()
case AES_256bit:
this.SetVersion(2, 0)
cf = NewCryptFilterAESV3()
cf = crypt.NewFilterAESV3()
default:
return fmt.Errorf("unsupported algorithm: %v", options.Algorithm)
}