Completed
Push — master ( 655053...db47c8 )
by Ben
02:17
created

EncryptionUtils::applyRc4Loop()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 14
rs 9.4286
cc 3
eloc 7
nc 3
nop 3
1
<?php
2
/**
3
 * BaconPdf
4
 *
5
 * @link      http://github.com/Bacon/BaconPdf For the canonical source repository
6
 * @copyright 2015 Ben 'DASPRiD' Scholzen
7
 * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
8
 */
9
10
namespace Bacon\Pdf\Utils;
11
12
/**
13
 * Utility methods for encrypting PDF documents.
14
 */
15
final class EncryptionUtils
16
{
17
    // @codingStandardsIgnoreStart
18
    const ENCRYPTION_PADDING = "\x28\xbf\x4e\x5e\x4e\x75\x8a\x41\x64\x00\x4e\x56\xff\xfa\x01\x08\x2e\x2e\x00\xb6\xd0\x68\x3e\x80\x2f\x0c\xa9\xfe\x64\x53\x69\x7a";
19
    // @codingStandardsIgnoreEnd
20
21
    private function __construct()
22
    {
23
    }
24
25
    /**
26
     * Computes the encryption key as defined by algorithm 3.2 in 3.5.2.
27
     *
28
     * @param  string $password
29
     * @param  int    $revision
30
     * @param  int    $keyLength
31
     * @param  string $ownerEntry
32
     * @param  int    $permissions
33
     * @param  string $idEntry
34
     * @param  bool   $encryptMetadata
35
     * @return string
36
     */
37
    public static function computeEncryptionKey(
38
        $password,
39
        $revision,
40
        $keyLength,
41
        $ownerEntry,
42
        $permissions,
43
        $idEntry,
44
        $encryptMetadata = true
45
    ) {
46
        $string = substr($password . self::ENCRYPTION_PADDING, 0, 32)
47
                . $ownerEntry
48
                . pack('V', $permissions)
49
                . $idEntry;
50
51
        if ($revision >= 4 && $encryptMetadata) {
52
            $string .= "\0xff\0xff\0xff\0xff";
53
        }
54
55
        $hash = hex2bin(md5($string));
56
57
        if ($revision >= 3) {
58
            for ($i = 0; $i < 50; ++$i) {
59
                $hash = hex2bin(md5(substr($hash, 0, $keyLength)));
60
            }
61
62
            return substr($hash, 0, $keyLength);
63
        }
64
65
        return substr($hash, 0, 5);
66
    }
67
68
    /**
69
     * Computes the owner entry as defined by algorithm 3.3 in 3.5.2.
70
     *
71
     * @param  string $ownerPassword
72
     * @param  string $userPassword
73
     * @param  int    $revision
74
     * @param  int    $keyLength
75
     * @return string
76
     */
77
    public static function computeOwnerEntry($ownerPassword, $userPassword, $revision, $keyLength)
78
    {
79
        $hash = hex2bin(md5(substr($ownerPassword . self::ENCRYPTION_PADDING, 0, 32)));
80
81
        if ($revision >= 3) {
82
            for ($i = 0; $i < 50; ++$i) {
83
                $hash = hex2bin(md5($hash));
84
            }
85
86
            $key = substr($hash, 0, $keyLength);
87
        } else {
88
            $key = substr($hash, 0, 5);
89
        }
90
91
        $value = self::rc4($key, substr($userPassword . self::ENCRYPTION_PADDING, 0, 32));
92
93
        if ($revision >= 3) {
94
            $value = self::applyRc4Loop($value, $key, $keyLength);
95
        }
96
97
        return $value;
98
    }
99
100
    /**
101
     * Computes the user entry (rev 2) as defined by algorithm 3.4 in 3.5.2.
102
     *
103
     * @param  string $userPassword
104
     * @param  string $ownerEntry
105
     * @param  int    $userPermissionFlags
106
     * @param  string $idEntry
107
     * @return string[]
108
     */
109
    public static function computeUserEntryRev2($userPassword, $ownerEntry, $userPermissionFlags, $idEntry)
110
    {
111
        $key = self::computeEncryptionKey($userPassword, 2, 5, $ownerEntry, $userPermissionFlags, $idEntry);
112
113
        return [self::rc4($key, self::ENCRYPTION_PADDING), $key];
114
    }
115
116
    /**
117
     * Computes the user entry (rev 3 or greater) as defined by algorithm 3.5 in 3.5.2.
118
     *
119
     * @param  string $userPassword
120
     * @param  int    $revision
121
     * @param  int    $keyLength
122
     * @param  string $ownerEntry
123
     * @param  int    $permissions
124
     * @param  string $idEntry
125
     * @return string[]
126
     */
127
    public static function computeUserEntryRev3OrGreater(
128
        $userPassword,
129
        $revision,
130
        $keyLength,
131
        $ownerEntry,
132
        $permissions,
133
        $idEntry
134
    ) {
135
        $key   = self::computeEncryptionKey($userPassword, $revision, $keyLength, $ownerEntry, $permissions, $idEntry);
136
        $hash  = hex2bin(md5(self::ENCRYPTION_PADDING . $idEntry));
137
        $value = self::applyRc4Loop(self::rc4($key, $hash), $key, $keyLength);
138
139
        return [$value . str_repeat("\x00", 16), $key];
140
    }
141
142
    /**
143
     * Native RC4 encryption.
144
     *
145
     * @param  string $key
146
     * @param  string $plaintext
147
     * @return string
148
     */
149
    public static function rc4($key, $plaintext)
150
    {
151
        // Key-scheduling algorithm
152
        $keyLength = strlen($key);
153
154
        $s = range(0, 255);
155
        $j = 0;
156
157
        for ($i = 0; $i <= 255; ++$i) {
158
            $j = ($j + $s[$i] + ord($key[$i % $keyLength]) % 256);
159
            list($s[$i], $s[$j]) = [$s[$j], $s[$i]];
160
        }
161
162
        // Pseudo-random generation algorithm
163
        $plaintextLength = strlen($plaintext);
164
        $result          = '';
165
166
        $i = 0;
167
        $j = 0;
168
169
        for ($x = 0; $x < $plaintextLength; ++$x) {
170
            $i = ($i + 1) % 256;
171
            $j = ($j + $s[$i]) % 256;
172
            list($s[$i], $s[$j]) = [$s[$j], $s[$i]];
173
174
            $k = $s[($s[$i] + $s[$j]) % 256];
175
            $result .= chr(ord($plaintext[$x]) ^ $k);
176
        }
177
178
        return $result;
179
    }
180
181
    /**
182
     * Applies loop RC4 encryption.
183
     *
184
     * @param  string $value
185
     * @param  string $key
186
     * @param  int    $keyLength
187
     * @return string
188
     */
189
    private static function applyRc4Loop($value, $key, $keyLength)
190
    {
191
        for ($i = 1; $i <= 19; ++$i) {
192
            $newKey = '';
193
194
            for ($j = 0; $j < $keyLength; ++$j) {
195
                $newKey = chr(ord($key[$j]) ^ $i);
196
            }
197
198
            $value = self::rc4($newKey, $value);
199
        }
200
201
        return $value;
202
    }
203
}
204