summaryrefslogtreecommitdiff
path: root/rsa/transform.py
blob: 5318dbf5347125c68cc6bbb370b87ab2fcbf97a5 (plain)
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# -*- coding: utf-8 -*-
#
#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

'''Data transformation functions.

From bytes to a number, number to bytes, etc.
'''

from __future__ import absolute_import

import binascii
from struct import pack
from rsa import common
from rsa._compat import is_integer, b, byte, get_machine_alignment


ZERO_BYTE = b('\x00')


def bytes2int(raw_bytes):
    r"""Converts a list of bytes or an 8-bit string to an integer.

    When using unicode strings, encode it to some encoding like UTF8 first.

    >>> (((128 * 256) + 64) * 256) + 15
    8405007
    >>> bytes2int('\x80@\x0f')
    8405007

    """

    return int(binascii.hexlify(raw_bytes), 16)


def _int2bytes(number, block_size=0):
    """Converts a number to a string of bytes.

    @param number: the number to convert
    @param block_size: the number of bytes to output. If the number encoded to
        bytes is less than this, the block will be zero-padded. When not given,
        the returned block is not padded.

    @throws OverflowError when block_size is given and the number takes up more
        bytes than fit into the block.

    >>> _int2bytes(123456789)
    b'\x07[\xcd\x15'
    >>> bytes2int(int2bytes(123456789))
    123456789

    >>> _int2bytes(123456789, 6)
    b'\x00\x00\x07[\xcd\x15'
    >>> bytes2int(int2bytes(123456789, 128))
    123456789

    >>> _int2bytes(123456789, 3)
    Traceback (most recent call last):
    ...
    OverflowError: Needed 4 bytes for number, but block size is 3

    """
    # Type checking
    if not is_integer(number):
        raise TypeError("You must pass an integer for 'number', not %s" %
            number.__class__)

    if number < 0:
        raise ValueError('Negative numbers cannot be used: %i' % number)

    # Do some bounds checking
    needed_bytes = common.byte_size(number)
    if block_size > 0:
        if needed_bytes > block_size:
            raise OverflowError('Needed %i bytes for number, but block size '
                'is %i' % (needed_bytes, block_size))

    # Convert the number to bytes.
    raw_bytes = []
    while number > 0:
        raw_bytes.insert(0, byte(number & 0xFF))
        number >>= 8

    # Pad with zeroes to fill the block
    if block_size > 0:
        padding = (block_size - needed_bytes) * ZERO_BYTE
    else:
        padding = b('')

    return padding + b('').join(raw_bytes)



def int2bytes(number, chunk_size=0,
                     _zero_byte=ZERO_BYTE,
                     _get_machine_alignment=get_machine_alignment):
    """
    Convert a integer to bytes (base-256 representation)::

        int2bytes(n:int, chunk_size:int) : string

    .. WARNING:
        Does not preserve leading zeros if you don't specify a chunk size.

    Usage::
    
        >>> int2bytes(123456789)
        b'\x07[\xcd\x15'
        >>> bytes2int(int2bytes(123456789))
        123456789

        >>> int2bytes(123456789, 6)
        b'\x00\x00\x07[\xcd\x15'
        >>> bytes2int(int2bytes(123456789, 128))
        123456789

        >>> int2bytes(123456789, 3)
        Traceback (most recent call last):
        ...
        OverflowError: Need 4 bytes for number, but block size is 3

    :param number:
        Integer value
    :param chunk_size:
        If optional chunk size is given and greater than zero, pad the front of
        the byte string with binary zeros so that the length is a multiple of
        ``chunk_size``. Raises an OverflowError if the chunk_size is not
        sufficient to represent the integer.
    :returns:
        Raw bytes (base-256 representation).
    :raises:
        ``OverflowError`` when block_size is given and the number takes up more
        bytes than fit into the block.
    """
    # Machine word-aligned implementation.
    # ~19x faster than naive implementation on 32-bit processors.
    # ~33x faster than naive implementation on 64-bit processors.
    # ~50x faster on 64-bit pypy 1.5

    # Don't need to raise TypeError ourselves. The code does that already
    # if a bad type is passed in as argument.

    if number < 0:
        raise ValueError('Number must be unsigned integer: %d' % number)

    raw_bytes = b('')
    if not number:
        # 0 == '\x00'
        raw_bytes = _zero_byte

    # Align packing to machine word size.
    num = number
    word_bits, word_bytes, max_uint, pack_type = _get_machine_alignment(num)
    pack_format = ">" + pack_type
    while num > 0:
        raw_bytes = pack(pack_format, num & max_uint) + raw_bytes
        num >>= word_bits

    # Count the number of zero prefix bytes.
    zero_leading = 0
    for zero_leading, x in enumerate(raw_bytes):
        if x != _zero_byte[0]:
            break

    if chunk_size > 0:
        # Bounds checking. We're not doing this up-front because the
        # most common use case is not specifying a chunk size. In the worst
        # case, the number will already have been converted to bytes above.
        #length = count * word_bytes
        length = len(raw_bytes)
        bytes_needed = length - zero_leading
        if bytes_needed > chunk_size:
            raise OverflowError(
                "Need %d bytes for number, but chunk size is %d" %
                (bytes_needed, chunk_size)
            )
        remainder = length % chunk_size
        if remainder:
            raw_bytes = (chunk_size - remainder) * _zero_byte + raw_bytes
    else:
        raw_bytes = raw_bytes[zero_leading:]
    return raw_bytes


if __name__ == '__main__':
    import doctest
    doctest.testmod()