summaryrefslogtreecommitdiff
path: root/rsa/__init__.py
blob: b66fe37c2f1ce74d10e7f25f8bea66e307eb4ee6 (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
"""RSA module

Module for calculating large primes, and RSA encryption, decryption, signing
and verification. Includes generating public and private keys.

WARNING: this implementation does not use random padding, compression of the
cleartext input to prevent repetitions, or other common security improvements.
Use with care.

If you want to have a more secure implementation, use the functions from the
``rsa.pkcs1`` module.

"""

__author__ = "Sybren Stuvel, Marloes de Boer, Ivo Tamboer, and Barry Mead"
__date__ = "2010-02-08"
__version__ = '2.1-beta0'

from rsa import transform
from rsa import common

from rsa.keygen import newkeys
from rsa.core import encrypt_int, decrypt_int

def encode64chops(chops):
    """base64encodes chops and combines them into a ',' delimited string"""

    # chips are character chops
    chips = [transform.int2str64(chop) for chop in chops]

    # delimit chops with comma
    encoded = ','.join(chips)

    return encoded

def decode64chops(string):
    """base64decodes and makes a ',' delimited string into chops"""

    # split chops at commas
    chips = string.split(',')

    # make character chips into numeric chops
    chops = [transform.str642int(chip) for chip in chips]

    return chops

def block_size(n):
    '''Returns the block size in bytes, given the public key.

    The block size is determined by the 'n=p*q' component of the key.
    '''

    # Set aside 2 bits so setting of safebit won't overflow modulo n.
    nbits = common.bit_size(n) - 2
    nbytes = nbits / 8

    return nbytes


def chopstring(message, key, n, int_op):
    """Chops the 'message' into integers that fit into n.
    
    Leaves room for a safebit to be added to ensure that all messages fold
    during exponentiation. The MSB of the number n is not independent modulo n
    (setting it could cause overflow), so use the next lower bit for the
    safebit. Therefore this function reserves 2 bits in the number n for
    non-data bits.

    Calls specified encryption function 'int_op' for each chop before storing.

    Used by 'encrypt' and 'sign'.
    """


    nbytes = block_size(n)

    msglen = len(message)
    blocks = msglen / nbytes

    if msglen % nbytes > 0:
        blocks += 1

    cypher = []
    
    for bindex in range(blocks):
        offset = bindex * nbytes
        block = message[offset:offset + nbytes]

        value = transform.bytes2int(block)
        to_store = int_op(value, key, n)

        cypher.append(to_store)

    return encode64chops(cypher)   #Encode encrypted ints to base64 strings

def gluechops(string, key, n, funcref):
    """Glues chops back together into a string.  calls
    funcref(integer, key, n) for each chop.

    Used by 'decrypt' and 'verify'.
    """

    messageparts = []
    chops = decode64chops(string)  #Decode base64 strings into integer chops

    nbytes = block_size(n)
    
    for chop in chops:
        value = funcref(chop, key, n) #Decrypt each chop
        block = transform.int2bytes(value)

        # Pad block with 0-bytes until we have reached the block size
        blocksize = len(block)
        padsize = nbytes - blocksize
        if padsize < 0:
            raise ValueError('Block larger than block size (%i > %i)!' %
                    (blocksize, nbytes))
        elif padsize > 0:
            block = '\x00' * padsize + block

        messageparts.append(block)

    # Combine decrypted strings into a msg
    return ''.join(messageparts)

def encrypt(message, key):
    """Encrypts a string 'message' with the public key 'key'"""
    if 'n' not in key:
        raise Exception("You must use the public key with encrypt")

    return chopstring(message, key['e'], key['n'], encrypt_int)

def sign(message, key):
    """Signs a string 'message' with the private key 'key'"""
    if 'p' not in key:
        raise Exception("You must use the private key with sign")

    return chopstring(message, key['d'], key['p']*key['q'], encrypt_int)

def decrypt(cypher, key):
    """Decrypts a string 'cypher' with the private key 'key'"""
    if 'p' not in key:
        raise Exception("You must use the private key with decrypt")

    return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int)

def verify(cypher, key):
    """Verifies a string 'cypher' with the public key 'key'"""
    if 'n' not in key:
        raise Exception("You must use the public key with verify")

    return gluechops(cypher, key['e'], key['n'], decrypt_int)

# Do doctest if we're run directly
if __name__ == "__main__":
    import doctest
    doctest.testmod()

__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify"]