1<?php
2/*
3 * Copyright 2008-2010 GuardTime AS
4 *
5 * This file is part of the GuardTime PHP SDK.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *     http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20/**
21 * @package tsp
22 */
23
24/**
25 * Data hash object used as seed when creating and verifying timestamps.
26 *
27 * To calculate the hash sum of some data, first create a data hash object and then
28 * add data to it by using the update methods. Do this until all the data has been
29 * fed to the hash calculator. You can then retrieve the resulting hash value using
30 * getHashedMessage() method but this closes the hash object and you can't add more
31 * data after that.
32 *
33 * Example 1 - new hash:
34 *
35 * <code>
36 * $stream = fopen('http://www.guardtime.com/data.txt', 'r');
37 *
38 * $hash = new GTDataHash(GTHashAlgorithm::getByName('DEFAULT'));
39 * $hash->update(array(0, 1, 2));
40 * $hash->updateFile('data.txt');
41 * $hahs->updateStream($stream);
42 * $hash->close();
43 *
44 * print_r($hash->getHashedMessage());
45 * </code>
46 *
47 * Example 2 - hash with existing hash value:
48 *
49 * <code>
50 * $bytes = array(
51 *  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
52 *  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1
53 * );
54 *
55 * $hash = new GTDataHash(GTHashAlgorithm::getByName('SHA256'), $bytes);
56 * </code>
57 *
58 * Example 3 - hash from dataImprint:
59 *
60 * <code>
61 * $dataImprint = array(
62 *  GTHashAlgorithm::getByName('SHA256')->getGtid(),
63 *  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
64 *  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1
65 * );
66 *
67 * $hash = GTDataHash::getInstance($dataImprint);
68 * </code>
69 *
70 * @package tsp
71 */
72class GTDataHash {
73
74    private $hashContext;
75    private $hashAlgorithm;
76    private $hashedMessage;
77
78    /**
79     * Builds a new hash object with the given hashAlgorithm and hashedMessage.
80     *
81     * When hashedMessage is null the GTDataHash will be open and it will be
82     * possible to add more data using update methods.
83     *
84     * When hashedMessage is specified the GTDataHash will be closed and it's
85     * not possible to add more data using update methods.
86     *
87     *
88     * @throws GTException
89     * @param  GTHashAlgorithm $hashAlgorithm hash algorithm to use
90     * @param  array $hashedMessage hash bytes
91     */
92    public function __construct($hashAlgorithm, $hashedMessage = null) {
93
94        if (empty($hashAlgorithm)) {
95            throw new GTException("parameter hashAlgorithm must not be empty");
96        }
97
98        if (!($hashAlgorithm instanceof GTHashAlgorithm)) {
99            throw new GTException("parameter hashAlgorithm must be an instance of GTHashAlgorithm");
100        }
101
102        $this->hashAlgorithm = $hashAlgorithm;
103
104        if ($hashedMessage == null) {
105
106            $this->hashContext = hash_init($this->hashAlgorithm->getName());
107
108            if (!is_resource($this->hashContext)) {
109                throw new GTException("Unable to init GTDataHash with algorithm: {$this->hashAlgorithm->getName()}");
110            }
111
112        } else {
113
114            if (!is_array($hashedMessage)) {
115                throw new GTException("hash must be an array of bytes");
116            }
117
118            if (count($hashedMessage) != $this->hashAlgorithm->getLength()) {
119                throw new GTException("hash length does not match with that defined in hash algorithm");
120            }
121
122            $this->hashedMessage = $hashedMessage;
123
124        }
125
126    }
127
128    /**
129     * Updates this hash calculator with the given bytes.
130     *
131     * @throws GTException
132     * @param  $bytes byte array to feed to this hash calculator
133     * @return void
134     */
135    public function update($bytes) {
136
137        if ($this->isClosed()) {
138            throw new GTException("hash calculator already closed");
139        }
140
141        if (empty($bytes)) {
142            throw new GTException("parameter bytes must not be empty");
143        }
144
145        $result = hash_update($this->hashContext, GTUtil::fromByteArray($bytes));
146
147        if (!$result) {
148            throw new GTException("hash update failed with unknown error");
149        }
150    }
151
152    /**
153     * Updates this hash calculator with the bytes from the given file.
154     *
155     * @throws GTException
156     * @param  string $file file to feed to this hash calculator
157     * @return void
158     */
159    public function updateFile($file) {
160
161        if ($this->isClosed()) {
162            throw new GTException("hash calculator already closed");
163        }
164
165        if (empty($file)) {
166            throw new GTException("parameter file must not be empty");
167        }
168
169        if (!is_file($file)) {
170            throw new GTException("file must be a file");
171        }
172
173        if (!is_readable($file)) {
174            throw new GTException("file must be readable");
175        }
176
177        if (filesize($file) == 0) {
178            throw new GTException("file must not be 0 bytes");
179        }
180
181        $result = hash_update_file($this->hashContext, $file);
182
183        if (!$result) {
184            throw new GTException("hash updateFile failed with unknown error");
185        }
186
187    }
188
189    /**
190     * Updates this hash calculator with the bytes from the given stream.
191     *
192     * @throws GTException
193     * @param  $handle handle of the stream to feed to this hash calculator
194     * @return void
195     */
196    public function updateStream($handle) {
197
198        if ($this->isClosed()) {
199            throw new GTException("hash calculator already closed");
200        }
201
202        if (empty($handle)) {
203            throw new GTException("parameter handle must not be empty");
204        }
205
206        if (!is_resource($handle)) {
207            throw new GTException("parameter handle must be a resource");
208        }
209
210        $result = hash_update_stream($this->hashContext, $handle);
211
212        if (!$result) {
213            throw new GTException("hash updateStream failed with unknown error");
214        }
215
216    }
217
218    /**
219     * Explicitly closes this hash calculator.
220     *
221     * After the hash calculator is closed all calls to update methods will throw an exception.
222     *
223     * @return GTDataHash
224     */
225    public function close() {
226
227        if (!$this->isClosed()) {
228
229            $this->hashedMessage = GTUtil::toByteArray(hash_final($this->hashContext, true));
230            $this->hashContext = null;
231
232        }
233
234        return $this;
235    }
236
237    /**
238     * Checks if this hash calculator is closed.
239     *
240     * @return bool true if this hash calculator is closed.
241     */
242    public function isClosed() {
243        return $this->hashContext == null;
244    }
245
246    /**
247     * Gets the GTHashAlgorithm used by this hash calculator.
248     *
249     * @return GTHashAlgorithm the currently used algorithm
250     */
251    public function getHashAlgorithm() {
252        return $this->hashAlgorithm;
253    }
254
255    /**
256     * Gets the current hash bytes.
257     *
258     * This method will also close the hash calculator when called. After closing
259     * all update() methods will throw an exception.
260     *
261     * @return array
262     */
263    public function getHashedMessage() {
264
265        if ($this->isClosed()) {
266            $this->close();
267        }
268
269        return $this->hashedMessage;
270    }
271
272    /**
273     * Gets the current hash data imprint.
274     *
275     * This method will also close the hash calculator when called. After closing
276     * all update() methods will throw an exception.
277     *
278     * Data imprint is an array of bytes consisting of GTHashAlgorithm->getGtid() + hash bytes
279     *
280     * @return array the data imprint
281     */
282    public function getDataImprint() {
283
284        if (!$this->isClosed()) {
285            $this->close();
286        }
287
288        $result = array($this->hashAlgorithm->getGtid());
289
290        foreach ($this->hashedMessage as $byte) {
291            array_push($result, $byte);
292        }
293
294        return $result;
295    }
296
297    /**
298     * Gets a GTDataHash instance from a $dataImprint.
299     *
300     * DataImprint is an array of bytes consisting of GTHashAlgorithm->getGtid() and hash bytes.
301     *
302     * <code>
303     * $dataImprint = array(
304     *   GTHashAlgorithm::getByName('SHA256')->getGtid(),
305     *   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
306     *   0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1
307     * );
308     * </code>
309     *
310     * @static
311     * @throws GTException
312     * @param  array $dataImprint dataImprint bytes
313     * @return GTDataHash
314     */
315    public static function getInstance($dataImprint) {
316
317        if (empty($dataImprint)) {
318            throw new GTException("parameter dataImprint must not be empty");
319        }
320
321        if (!is_array($dataImprint)) {
322            throw new GTException("parameter dataImprint must be an array of bytes");
323        }
324
325        if (count($dataImprint) < 2) {
326            throw new GTException("parameter dataImprint must be at least 2 bytes long");
327        }
328
329        $hashAlgorithm = GTHashAlgorithm::getByGtid($dataImprint[0]);
330        $hashedMessage = array_slice($dataImprint, 1);
331
332        return new GTDataHash($hashAlgorithm, $hashedMessage);
333    }
334
335}
336
337?>
338