File size: 3,028 Bytes
d94b56e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# -*- coding: utf-8 -*-
#
#   pyhwp : hwp file format parser in python
#   Copyright (C) 2010-2023 mete0r <https://github.com/mete0r>
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU Affero General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
''' Decode distribute docs.

Based on the algorithm described by Changwoo Ryu
See https://groups.google.com/forum/#!topic/hwp-foss/d2KL2ypR89Q
'''
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals
from io import BytesIO
import logging

from .plat import get_aes128ecb_decrypt
from .recordstream import read_record
from .tagids import HWPTAG_DISTRIBUTE_DOC_DATA

logger = logging.getLogger(__name__)


def decode(stream):
    distdoc_data_record = read_record(stream, 0)
    if distdoc_data_record['tagid'] != HWPTAG_DISTRIBUTE_DOC_DATA:
        raise IOError('the first record is not an HWPTAG_DISTRIBUTE_DOC_DATA')
    distdoc_data = distdoc_data_record['payload']
    key = decode_head_to_key(distdoc_data)
    tail = stream.read()
    decrypted = decrypt_tail(key, tail)
    return BytesIO(decrypted)


class Random:
    ''' MSVC's srand()/rand() like pseudorandom generator.
    '''

    def __init__(self, seed):
        self.seed = seed

    def rand(self):
        self.seed = (self.seed * 214013 + 2531011) & 0xffffffff
        value = (self.seed >> 16) & 0x7fff
        return value


def decode_head_to_sha1(record_payload):
    ''' Decode HWPTAG_DISTRIBUTE_DOC_DATA.

    It's the sha1 digest of user-supplied password string, i.e.,

        '12345' -> hashlib.sha1('12345').digest()

    '''
    if len(record_payload) != 256:
        raise ValueError('payload size must be 256 bytes')

    data = bytearray(record_payload)
    seed = data[3] << 24 | data[2] << 16 | data[1] << 8 | data[0]
    random = Random(seed)

    n = 0
    for i in range(256):
        if n == 0:
            key = random.rand() & 0xff
            n = (random.rand() & 0xf) + 1
        if i >= 4:
            data[i] = data[i] ^ key
        n -= 1

    # decoded = b''.join(chr(x) for x in data)
    decoded = data
    sha1offset = 4 + (seed & 0xf)

    ucs16le = decoded[sha1offset:sha1offset + 80]
    return ucs16le


def decode_head_to_key(record_payload):
    sha1ucs16le = decode_head_to_sha1(record_payload)
    return sha1ucs16le[:16]


def decrypt_tail(key, encrypted_tail):
    decrypt = get_aes128ecb_decrypt()
    return decrypt(key, encrypted_tail)