Skip to content

Commit 067897d

Browse files
committed
Cleanup encryption
1 parent 6e1af9e commit 067897d

File tree

9 files changed

+220
-85
lines changed

9 files changed

+220
-85
lines changed

cmd/pdfcpu/prepare.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,20 +389,33 @@ func prepareDecryptCommand(config *pdfcpu.Configuration) *api.Command {
389389
return api.DecryptCommand(filenameIn, filenameOut, config)
390390
}
391391

392-
func validEncryptOptions() bool {
393-
return pageSelection == "" &&
394-
(mode == "" || mode == "rc4" || mode == "aes") &&
395-
(key == "" || key == "40" || key == "128") &&
396-
(perm == "" || perm == "none" || perm == "all")
392+
func validateEncryptFlags() {
393+
394+
if mode != "rc4" && mode != "aes" && mode != "" {
395+
fmt.Fprintf(os.Stderr, "%s\n\n", "valid modes: rc4,aes default:aes")
396+
os.Exit(1)
397+
}
398+
399+
if key != "40" && key != "128" && key != "" {
400+
fmt.Fprintf(os.Stderr, "%s\n\n", "supported key lengths: 40,128 default:128")
401+
os.Exit(1)
402+
}
403+
404+
if perm != "none" && perm != "all" && perm != "" {
405+
fmt.Fprintf(os.Stderr, "%s\n\n", "supported permissions: none,all default:none (viewing is always allowed!)")
406+
os.Exit(1)
407+
}
397408
}
398409

399410
func prepareEncryptCommand(config *pdfcpu.Configuration) *api.Command {
400411

401-
if len(flag.Args()) == 0 || len(flag.Args()) > 2 || !validEncryptOptions() {
412+
if len(flag.Args()) == 0 || len(flag.Args()) > 2 {
402413
fmt.Fprintf(os.Stderr, "%s\n\n", usageEncrypt)
403414
os.Exit(1)
404415
}
405416

417+
validateEncryptFlags()
418+
406419
if mode == "rc4" {
407420
config.EncryptUsingAES = false
408421
}

cmd/pdfcpu/usage.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ verbose, v ... turn on logging
218218
vv ... verbose logging
219219
upw ... user password, required unless = ""
220220
inFile ... input pdf file
221-
opwOld ... old owner password (supply user password on initial changeopw)
221+
opwOld ... old owner password (provide user password on initial changeopw)
222222
opwNew ... new owner password`
223223

224224
usageWMDescription = `<description> is a comma separated configuration string containing:
@@ -249,24 +249,28 @@ e.g. 'Draft' 'logo.png'
249249
'Intentionally left blank, s:.75 abs, p:48' 'some.pdf, r:45'
250250
'Confidental, f:Courier, s:0.75, c: 0.5 0.0 0.0, r:20' 'some.pdf:3, r:-90, s:0.75'`
251251

252-
usageStamp = "usage: pdfcpu stamp [-v(erbose)|vv] [-pages pageSelection] description inFile [outFile]"
252+
usageStamp = "usage: pdfcpu stamp [-v(erbose)|vv] [-pages pageSelection] [-upw userpw] [-opw ownerpw] description inFile [outFile]"
253253
usageLongStamp = `Stamp adds stamps for selected pages.
254254
255255
verbose, v ... turn on logging
256256
vv ... verbose logging
257257
pages ... page selection
258+
upw ... user password
259+
opw ... owner password
258260
description ... font, font size, text, color, image/pdf file name, pdf page#, rotation, opacity, scale factor, render mode
259261
inFile ... input pdf file
260262
outFile ... output pdf file (default: inFile-new.pdf)
261263
262264
` + usageWMDescription
263265

264-
usageWatermark = "usage: pdfcpu watermark [-v(erbose)|vv] [-pages pageSelection] description inFile [outFile]"
266+
usageWatermark = "usage: pdfcpu watermark [-v(erbose)|vv] [-pages pageSelection] [-upw userpw] [-opw ownerpw] description inFile [outFile]"
265267
usageLongWatermark = `Watermark adds watermarks for selected pages.
266268
267269
verbose, v ... turn on logging
268270
vv ... verbose logging
269271
pages ... page selection
272+
upw ... user password
273+
opw ... owner password
270274
description ... font, font size, text, color, image/pdf file name, pdf page#, rotation, opacity, scale factor, render mode
271275
inFile ... input pdf file
272276
outFile ... output pdf file (default: inFile-new.pdf)
@@ -309,12 +313,14 @@ description ... dimensions, format, position, offset, scale factor
309313
'd:300 600, p:bl, o:20 20, s:1.0 abs' ... render the image anchored to bottom left corner with offset 20,20 and abs. scaling 1.0.
310314
'p:full' ... render the image to a page with corresponding dimensions.`
311315

312-
usageRotate = "usage: pdfcpu rotate [-v(erbose)|vv] [-pages pageSelection] inFile rotation"
316+
usageRotate = "usage: pdfcpu rotate [-v(erbose)|vv] [-pages pageSelection] [-upw userpw] [-opw ownerpw] inFile rotation"
313317
usageLongRotate = `Rotate rotates selected pages.
314318
315319
verbose, v ... turn on logging
316320
vv ... verbose logging
317321
pages ... page selection
322+
upw ... user password
323+
opw ... owner password
318324
inFile ... input pdf file
319325
rotation ... a multiple of 90 degrees for clockwise rotation.`
320326

pkg/api/example_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ func exampleProcessChangeUserPW() {
267267

268268
config := pdfcpu.NewDefaultConfiguration()
269269

270-
// supply existing owner pw like so
270+
// Provide existing owner pw like so
271271
config.OwnerPW = "opw"
272272

273273
pwOld := "pwOld"
@@ -283,7 +283,7 @@ func exampleProcessChangeOwnerPW() {
283283

284284
config := pdfcpu.NewDefaultConfiguration()
285285

286-
// supply existing user pw like so
286+
// Provide existing user pw like so
287287
config.UserPW = "upw"
288288

289289
// old and new owner pw

pkg/pdfcpu/crypto.go

Lines changed: 130 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var (
6565
LISTPERMISSIONS: {0, 0},
6666
ADDPERMISSIONS: {0, 0},
6767
ADDWATERMARKS: {1, 0},
68+
//DECRYPT: {1, 0},
6869
}
6970
)
7071

@@ -185,7 +186,18 @@ func validateUserPassword(ctx *Context) (ok bool, key []byte, err error) {
185186

186187
//fmt.Printf("validateUserPassword: u =\n%v\n", u)
187188

188-
return bytes.HasPrefix(ctx.E.U, u), key, nil
189+
match := false
190+
191+
switch ctx.E.R {
192+
193+
case 2:
194+
match = bytes.Equal(ctx.E.U, u)
195+
196+
case 3, 4:
197+
match = bytes.HasPrefix(ctx.E.U, u[:16])
198+
}
199+
200+
return match, key, nil
189201
}
190202

191203
func key(ownerpw, userpw string, r, l int) (key []byte) {
@@ -416,7 +428,7 @@ func supportedCFEntry(d Dict) (bool, error) {
416428

417429
func perms(p int) (list []string) {
418430

419-
list = append(list, fmt.Sprintf("%0b", uint32(p)&0x0F3C))
431+
list = append(list, fmt.Sprintf("permission bits: %12b", uint32(p)&0x0F3C))
420432
list = append(list, fmt.Sprintf("Bit 3: %t (print(rev2), print quality(rev>=3))", p&0x0004 > 0))
421433
list = append(list, fmt.Sprintf("Bit 4: %t (modify other than controlled by bits 6,9,11)", p&0x0008 > 0))
422434
list = append(list, fmt.Sprintf("Bit 5: %t (extract(rev2), extract other than controlled by bit 10(rev>=3))", p&0x0010 > 0))
@@ -717,32 +729,59 @@ func decryptKey(objNumber, generation int, key []byte, aes bool) []byte {
717729
return dk
718730
}
719731

720-
// EncryptString encrypts s using RC4 or AES.
721-
func encryptString(needAES bool, s string, objNr, genNr int, key []byte) (*string, error) {
732+
// EncryptBytes encrypts s using RC4 or AES.
733+
func encryptBytes(needAES bool, b []byte, objNr, genNr int, key []byte) ([]byte, error) {
722734

723-
log.Debug.Printf("EncryptString begin obj:%d gen:%d key:%X aes:%t\n<%s>\n", objNr, genNr, key, needAES, s)
735+
log.Debug.Printf("EncryptBytes begin obj:%d gen:%d key:%X aes:%t\n<%v>\n", objNr, genNr, key, needAES, b)
724736

725-
var s1 *string
726-
var err error
727737
k := decryptKey(objNr, genNr, key, needAES)
728738
//logInfoCrypto.Printf("EncryptString k = %v\n", k)
729739

730740
if needAES {
731-
b, err := encryptAESBytes([]byte(s), k)
741+
bb, err := encryptAESBytes(b, k)
732742
if err != nil {
733743
return nil, err
734744
}
735-
sb := string(b)
736-
s1 = &sb
745+
return bb, nil
746+
}
737747

738-
} else {
739-
s1, err = applyRC4Cipher([]byte(s), objNr, genNr, key, needAES)
748+
return applyRC4CipherBytes(b, objNr, genNr, key, needAES)
749+
}
750+
751+
// EncryptString encrypts s using RC4 or AES.
752+
func encryptString(needAES bool, s string, objNr, genNr int, key []byte) (*string, error) {
753+
754+
log.Debug.Printf("encryptString begin obj:%d gen:%d key:%X aes:%t\n<%s>\n", objNr, genNr, key, needAES, s)
755+
756+
b, err := encryptBytes(needAES, []byte(s), objNr, genNr, key)
757+
if err != nil {
758+
return nil, err
759+
}
760+
761+
s1, err := Escape(string(b))
762+
if err != nil {
763+
return nil, err
764+
}
765+
766+
return s1, err
767+
}
768+
769+
// DecryptBytes decrypts bb using RC4 or AES.
770+
func decryptBytes(needAES bool, b []byte, objNr, genNr int, key []byte) ([]byte, error) {
771+
772+
log.Debug.Printf("DecryptBytes begin obj:%d gen:%d key:%X aes:%t bb:%v\n", objNr, genNr, key, needAES, b)
773+
774+
k := decryptKey(objNr, genNr, key, needAES)
775+
776+
if needAES {
777+
bb, err := decryptAESBytes(b, k)
740778
if err != nil {
741779
return nil, err
742780
}
781+
return bb, nil
743782
}
744783

745-
return Escape(*s1)
784+
return applyRC4CipherBytes(b, objNr, genNr, key, needAES)
746785
}
747786

748787
// DecryptString decrypts s using RC4 or AES.
@@ -755,18 +794,29 @@ func decryptString(needAES bool, s string, objNr, genNr int, key []byte) (*strin
755794
return nil, err
756795
}
757796

758-
k := decryptKey(objNr, genNr, key, needAES)
797+
b, err = decryptBytes(needAES, b, objNr, genNr, key)
798+
if err != nil {
799+
return nil, err
800+
}
759801

760-
if needAES {
761-
b, err = decryptAESBytes(b, k)
762-
if err != nil {
763-
return nil, err
764-
}
765-
s1 := string(b)
766-
return &s1, nil
802+
s1 := string(b)
803+
return &s1, nil
804+
}
805+
806+
func applyRC4CipherBytes(b []byte, objNr, genNr int, key []byte, needAES bool) ([]byte, error) {
807+
808+
log.Debug.Printf("applyRC4CipherBytes begin b:<%v> %d %d key:%X aes:%t\n", b, objNr, genNr, key, needAES)
809+
810+
c, err := rc4.NewCipher(decryptKey(objNr, genNr, key, needAES))
811+
if err != nil {
812+
return nil, err
767813
}
768814

769-
return applyRC4Cipher(b, objNr, genNr, key, needAES)
815+
c.XORKeyStream(b, b)
816+
817+
log.Debug.Printf("applyRC4Cipher end, rc4 returning: <%v>\n", b)
818+
819+
return b, nil
770820
}
771821

772822
func applyRC4Cipher(b []byte, objNr, genNr int, key []byte, needAES bool) (*string, error) {
@@ -799,8 +849,20 @@ func encrypt(m map[string]Object, k string, v Object, objNr, genNr int, key []by
799849
return nil
800850
}
801851

852+
func encryptDict(d Dict, objNr, genNr int, key []byte, aes bool) error {
853+
854+
for k, v := range d {
855+
err := encrypt(d, k, v, objNr, genNr, key, aes)
856+
if err != nil {
857+
return err
858+
}
859+
}
860+
861+
return nil
862+
}
863+
802864
// EncryptDeepObject recurses over non trivial PDF objects and encrypts all strings encountered.
803-
func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*StringLiteral, error) {
865+
func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*HexLiteral, error) {
804866

805867
_, ok := objIn.(IndirectRef)
806868
if ok {
@@ -810,19 +872,15 @@ func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*S
810872
switch obj := objIn.(type) {
811873

812874
case StreamDict:
813-
for k, v := range obj.Dict {
814-
err := encrypt(obj.Dict, k, v, objNr, genNr, key, aes)
815-
if err != nil {
816-
return nil, err
817-
}
875+
err := encryptDict(obj.Dict, objNr, genNr, key, aes)
876+
if err != nil {
877+
return nil, err
818878
}
819879

820880
case Dict:
821-
for k, v := range obj {
822-
err := encrypt(obj, k, v, objNr, genNr, key, aes)
823-
if err != nil {
824-
return nil, err
825-
}
881+
err := encryptDict(obj, objNr, genNr, key, aes)
882+
if err != nil {
883+
return nil, err
826884
}
827885

828886
case Array:
@@ -837,14 +895,21 @@ func encryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*S
837895
}
838896

839897
case StringLiteral:
840-
s, err := encryptString(aes, obj.Value(), objNr, genNr, key)
898+
s := obj.Value()
899+
b, err := encryptBytes(aes, []byte(s), objNr, genNr, key)
841900
if err != nil {
842901
return nil, err
843902
}
903+
hl := NewHexLiteral(b)
904+
return &hl, nil
844905

845-
sl := StringLiteral(*s)
846-
847-
return &sl, nil
906+
case HexLiteral:
907+
bb, err := encryptHexLiteral(obj, aes, objNr, genNr, key)
908+
if err != nil {
909+
return nil, err
910+
}
911+
hl := NewHexLiteral(bb)
912+
return &hl, nil
848913

849914
default:
850915

@@ -890,9 +955,15 @@ func decryptDeepObject(objIn Object, objNr, genNr int, key []byte, aes bool) (*S
890955
if err != nil {
891956
return nil, err
892957
}
893-
894958
sl := StringLiteral(*s)
959+
return &sl, nil
895960

961+
case HexLiteral:
962+
bb, err := decryptHexLiteral(obj, aes, objNr, genNr, key)
963+
if err != nil {
964+
return nil, err
965+
}
966+
sl := StringLiteral(string(bb))
896967
return &sl, nil
897968

898969
default:
@@ -985,7 +1056,7 @@ func encryptAESBytes(b, key []byte) ([]byte, error) {
9851056
mode := cipher.NewCBCEncrypter(cb, iv)
9861057
mode.CryptBlocks(data[aes.BlockSize:], b)
9871058

988-
//fmt.Printf("encryptAESBytes after:\n%s\n", hex.Dump(data))
1059+
//fmt.Printf("encryptAESBytes after:\nlen=%d %s\n", len(data), hex.Dump(data))
9891060

9901061
return data, nil
9911062
}
@@ -1064,3 +1135,23 @@ func fileID(ctx *Context) (HexLiteral, error) {
10641135

10651136
return HexLiteral(hex.EncodeToString(m)), nil
10661137
}
1138+
1139+
func encryptHexLiteral(hl HexLiteral, needAES bool, objNr, genNr int, key []byte) ([]byte, error) {
1140+
1141+
bb, err := hl.Bytes()
1142+
if err != nil {
1143+
return nil, err
1144+
}
1145+
1146+
return encryptBytes(needAES, bb, objNr, genNr, key)
1147+
}
1148+
1149+
func decryptHexLiteral(hl HexLiteral, needAES bool, objNr, genNr int, key []byte) ([]byte, error) {
1150+
1151+
bb, err := hl.Bytes()
1152+
if err != nil {
1153+
return nil, err
1154+
}
1155+
1156+
return decryptBytes(needAES, bb, objNr, genNr, key)
1157+
}

0 commit comments

Comments
 (0)