remove RSA folder
This commit is contained in:
parent
7e190c4d23
commit
3f6142ba31
|
@ -1,45 +0,0 @@
|
|||
# -*- 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.
|
||||
"""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, Barry Mead and Yesudeep Mangalapilly"
|
||||
__date__ = "2012-06-17"
|
||||
__version__ = '3.1.1'
|
||||
|
||||
from rsa.key import newkeys, PrivateKey, PublicKey
|
||||
from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \
|
||||
VerificationError
|
||||
|
||||
# Do doctest if we're run directly
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey',
|
||||
'PrivateKey', 'DecryptionError', 'VerificationError']
|
||||
|
160
rsa/_compat.py
160
rsa/_compat.py
|
@ -1,160 +0,0 @@
|
|||
# -*- 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.
|
||||
|
||||
"""Python compatibility wrappers."""
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
from struct import pack
|
||||
|
||||
try:
|
||||
MAX_INT = sys.maxsize
|
||||
except AttributeError:
|
||||
MAX_INT = sys.maxint
|
||||
|
||||
MAX_INT64 = (1 << 63) - 1
|
||||
MAX_INT32 = (1 << 31) - 1
|
||||
MAX_INT16 = (1 << 15) - 1
|
||||
|
||||
# Determine the word size of the processor.
|
||||
if MAX_INT == MAX_INT64:
|
||||
# 64-bit processor.
|
||||
MACHINE_WORD_SIZE = 64
|
||||
elif MAX_INT == MAX_INT32:
|
||||
# 32-bit processor.
|
||||
MACHINE_WORD_SIZE = 32
|
||||
else:
|
||||
# Else we just assume 64-bit processor keeping up with modern times.
|
||||
MACHINE_WORD_SIZE = 64
|
||||
|
||||
|
||||
try:
|
||||
# < Python3
|
||||
unicode_type = unicode
|
||||
have_python3 = False
|
||||
except NameError:
|
||||
# Python3.
|
||||
unicode_type = str
|
||||
have_python3 = True
|
||||
|
||||
# Fake byte literals.
|
||||
if str is unicode_type:
|
||||
def byte_literal(s):
|
||||
return s.encode('latin1')
|
||||
else:
|
||||
def byte_literal(s):
|
||||
return s
|
||||
|
||||
# ``long`` is no more. Do type detection using this instead.
|
||||
try:
|
||||
integer_types = (int, long)
|
||||
except NameError:
|
||||
integer_types = (int,)
|
||||
|
||||
b = byte_literal
|
||||
|
||||
try:
|
||||
# Python 2.6 or higher.
|
||||
bytes_type = bytes
|
||||
except NameError:
|
||||
# Python 2.5
|
||||
bytes_type = str
|
||||
|
||||
|
||||
# To avoid calling b() multiple times in tight loops.
|
||||
ZERO_BYTE = b('\x00')
|
||||
EMPTY_BYTE = b('')
|
||||
|
||||
|
||||
def is_bytes(obj):
|
||||
"""
|
||||
Determines whether the given value is a byte string.
|
||||
|
||||
:param obj:
|
||||
The value to test.
|
||||
:returns:
|
||||
``True`` if ``value`` is a byte string; ``False`` otherwise.
|
||||
"""
|
||||
return isinstance(obj, bytes_type)
|
||||
|
||||
|
||||
def is_integer(obj):
|
||||
"""
|
||||
Determines whether the given value is an integer.
|
||||
|
||||
:param obj:
|
||||
The value to test.
|
||||
:returns:
|
||||
``True`` if ``value`` is an integer; ``False`` otherwise.
|
||||
"""
|
||||
return isinstance(obj, integer_types)
|
||||
|
||||
|
||||
def byte(num):
|
||||
"""
|
||||
Converts a number between 0 and 255 (both inclusive) to a base-256 (byte)
|
||||
representation.
|
||||
|
||||
Use it as a replacement for ``chr`` where you are expecting a byte
|
||||
because this will work on all current versions of Python::
|
||||
|
||||
:param num:
|
||||
An unsigned integer between 0 and 255 (both inclusive).
|
||||
:returns:
|
||||
A single byte.
|
||||
"""
|
||||
return pack("B", num)
|
||||
|
||||
|
||||
def get_word_alignment(num, force_arch=64,
|
||||
_machine_word_size=MACHINE_WORD_SIZE):
|
||||
"""
|
||||
Returns alignment details for the given number based on the platform
|
||||
Python is running on.
|
||||
|
||||
:param num:
|
||||
Unsigned integral number.
|
||||
:param force_arch:
|
||||
If you don't want to use 64-bit unsigned chunks, set this to
|
||||
anything other than 64. 32-bit chunks will be preferred then.
|
||||
Default 64 will be used when on a 64-bit machine.
|
||||
:param _machine_word_size:
|
||||
(Internal) The machine word size used for alignment.
|
||||
:returns:
|
||||
4-tuple::
|
||||
|
||||
(word_bits, word_bytes,
|
||||
max_uint, packing_format_type)
|
||||
"""
|
||||
max_uint64 = 0xffffffffffffffff
|
||||
max_uint32 = 0xffffffff
|
||||
max_uint16 = 0xffff
|
||||
max_uint8 = 0xff
|
||||
|
||||
if force_arch == 64 and _machine_word_size >= 64 and num > max_uint32:
|
||||
# 64-bit unsigned integer.
|
||||
return 64, 8, max_uint64, "Q"
|
||||
elif num > max_uint16:
|
||||
# 32-bit unsigned integer
|
||||
return 32, 4, max_uint32, "L"
|
||||
elif num > max_uint8:
|
||||
# 16-bit unsigned integer.
|
||||
return 16, 2, max_uint16, "H"
|
||||
else:
|
||||
# 8-bit unsigned integer.
|
||||
return 8, 1, max_uint8, "B"
|
|
@ -1,442 +0,0 @@
|
|||
"""RSA module
|
||||
pri = k[1] //Private part of keys d,p,q
|
||||
|
||||
Module for calculating large primes, and RSA encryption, decryption,
|
||||
signing and verification. Includes generating public and private keys.
|
||||
|
||||
WARNING: this code implements the mathematics of RSA. It is not suitable for
|
||||
real-world secure cryptography purposes. It has not been reviewed by a security
|
||||
expert. It does not include padding of data. There are many ways in which the
|
||||
output of this module, when used without any modification, can be sucessfully
|
||||
attacked.
|
||||
"""
|
||||
|
||||
__author__ = "Sybren Stuvel, Marloes de Boer and Ivo Tamboer"
|
||||
__date__ = "2010-02-05"
|
||||
__version__ = '1.3.3'
|
||||
|
||||
# NOTE: Python's modulo can return negative numbers. We compensate for
|
||||
# this behaviour using the abs() function
|
||||
|
||||
from cPickle import dumps, loads
|
||||
import base64
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import types
|
||||
import zlib
|
||||
|
||||
from rsa._compat import byte
|
||||
|
||||
# Display a warning that this insecure version is imported.
|
||||
import warnings
|
||||
warnings.warn('Insecure version of the RSA module is imported as %s, be careful'
|
||||
% __name__)
|
||||
|
||||
def gcd(p, q):
|
||||
"""Returns the greatest common divisor of p and q
|
||||
|
||||
|
||||
>>> gcd(42, 6)
|
||||
6
|
||||
"""
|
||||
if p<q: return gcd(q, p)
|
||||
if q == 0: return p
|
||||
return gcd(q, abs(p%q))
|
||||
|
||||
def bytes2int(bytes):
|
||||
"""Converts a list of bytes or a string to an integer
|
||||
|
||||
>>> (128*256 + 64)*256 + + 15
|
||||
8405007
|
||||
>>> l = [128, 64, 15]
|
||||
>>> bytes2int(l)
|
||||
8405007
|
||||
"""
|
||||
|
||||
if not (type(bytes) is types.ListType or type(bytes) is types.StringType):
|
||||
raise TypeError("You must pass a string or a list")
|
||||
|
||||
# Convert byte stream to integer
|
||||
integer = 0
|
||||
for byte in bytes:
|
||||
integer *= 256
|
||||
if type(byte) is types.StringType: byte = ord(byte)
|
||||
integer += byte
|
||||
|
||||
return integer
|
||||
|
||||
def int2bytes(number):
|
||||
"""Converts a number to a string of bytes
|
||||
|
||||
>>> bytes2int(int2bytes(123456789))
|
||||
123456789
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
string = ""
|
||||
|
||||
while number > 0:
|
||||
string = "%s%s" % (byte(number & 0xFF), string)
|
||||
number /= 256
|
||||
|
||||
return string
|
||||
|
||||
def fast_exponentiation(a, p, n):
|
||||
"""Calculates r = a^p mod n
|
||||
"""
|
||||
result = a % n
|
||||
remainders = []
|
||||
while p != 1:
|
||||
remainders.append(p & 1)
|
||||
p = p >> 1
|
||||
while remainders:
|
||||
rem = remainders.pop()
|
||||
result = ((a ** rem) * result ** 2) % n
|
||||
return result
|
||||
|
||||
def read_random_int(nbits):
|
||||
"""Reads a random integer of approximately nbits bits rounded up
|
||||
to whole bytes"""
|
||||
|
||||
nbytes = ceil(nbits/8.)
|
||||
randomdata = os.urandom(nbytes)
|
||||
return bytes2int(randomdata)
|
||||
|
||||
def ceil(x):
|
||||
"""ceil(x) -> int(math.ceil(x))"""
|
||||
|
||||
return int(math.ceil(x))
|
||||
|
||||
def randint(minvalue, maxvalue):
|
||||
"""Returns a random integer x with minvalue <= x <= maxvalue"""
|
||||
|
||||
# Safety - get a lot of random data even if the range is fairly
|
||||
# small
|
||||
min_nbits = 32
|
||||
|
||||
# The range of the random numbers we need to generate
|
||||
range = maxvalue - minvalue
|
||||
|
||||
# Which is this number of bytes
|
||||
rangebytes = ceil(math.log(range, 2) / 8.)
|
||||
|
||||
# Convert to bits, but make sure it's always at least min_nbits*2
|
||||
rangebits = max(rangebytes * 8, min_nbits * 2)
|
||||
|
||||
# Take a random number of bits between min_nbits and rangebits
|
||||
nbits = random.randint(min_nbits, rangebits)
|
||||
|
||||
return (read_random_int(nbits) % range) + minvalue
|
||||
|
||||
def fermat_little_theorem(p):
|
||||
"""Returns 1 if p may be prime, and something else if p definitely
|
||||
is not prime"""
|
||||
|
||||
a = randint(1, p-1)
|
||||
return fast_exponentiation(a, p-1, p)
|
||||
|
||||
def jacobi(a, b):
|
||||
"""Calculates the value of the Jacobi symbol (a/b)
|
||||
"""
|
||||
|
||||
if a % b == 0:
|
||||
return 0
|
||||
result = 1
|
||||
while a > 1:
|
||||
if a & 1:
|
||||
if ((a-1)*(b-1) >> 2) & 1:
|
||||
result = -result
|
||||
b, a = a, b % a
|
||||
else:
|
||||
if ((b ** 2 - 1) >> 3) & 1:
|
||||
result = -result
|
||||
a = a >> 1
|
||||
return result
|
||||
|
||||
def jacobi_witness(x, n):
|
||||
"""Returns False if n is an Euler pseudo-prime with base x, and
|
||||
True otherwise.
|
||||
"""
|
||||
|
||||
j = jacobi(x, n) % n
|
||||
f = fast_exponentiation(x, (n-1)/2, n)
|
||||
|
||||
if j == f: return False
|
||||
return True
|
||||
|
||||
def randomized_primality_testing(n, k):
|
||||
"""Calculates whether n is composite (which is always correct) or
|
||||
prime (which is incorrect with error probability 2**-k)
|
||||
|
||||
Returns False if the number if composite, and True if it's
|
||||
probably prime.
|
||||
"""
|
||||
|
||||
q = 0.5 # Property of the jacobi_witness function
|
||||
|
||||
# t = int(math.ceil(k / math.log(1/q, 2)))
|
||||
t = ceil(k / math.log(1/q, 2))
|
||||
for i in range(t+1):
|
||||
x = randint(1, n-1)
|
||||
if jacobi_witness(x, n): return False
|
||||
|
||||
return True
|
||||
|
||||
def is_prime(number):
|
||||
"""Returns True if the number is prime, and False otherwise.
|
||||
|
||||
>>> is_prime(42)
|
||||
0
|
||||
>>> is_prime(41)
|
||||
1
|
||||
"""
|
||||
|
||||
"""
|
||||
if not fermat_little_theorem(number) == 1:
|
||||
# Not prime, according to Fermat's little theorem
|
||||
return False
|
||||
"""
|
||||
|
||||
if randomized_primality_testing(number, 5):
|
||||
# Prime, according to Jacobi
|
||||
return True
|
||||
|
||||
# Not prime
|
||||
return False
|
||||
|
||||
|
||||
def getprime(nbits):
|
||||
"""Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In
|
||||
other words: nbits is rounded up to whole bytes.
|
||||
|
||||
>>> p = getprime(8)
|
||||
>>> is_prime(p-1)
|
||||
0
|
||||
>>> is_prime(p)
|
||||
1
|
||||
>>> is_prime(p+1)
|
||||
0
|
||||
"""
|
||||
|
||||
nbytes = int(math.ceil(nbits/8.))
|
||||
|
||||
while True:
|
||||
integer = read_random_int(nbits)
|
||||
|
||||
# Make sure it's odd
|
||||
integer |= 1
|
||||
|
||||
# Test for primeness
|
||||
if is_prime(integer): break
|
||||
|
||||
# Retry if not prime
|
||||
|
||||
return integer
|
||||
|
||||
def are_relatively_prime(a, b):
|
||||
"""Returns True if a and b are relatively prime, and False if they
|
||||
are not.
|
||||
|
||||
>>> are_relatively_prime(2, 3)
|
||||
1
|
||||
>>> are_relatively_prime(2, 4)
|
||||
0
|
||||
"""
|
||||
|
||||
d = gcd(a, b)
|
||||
return (d == 1)
|
||||
|
||||
def find_p_q(nbits):
|
||||
"""Returns a tuple of two different primes of nbits bits"""
|
||||
|
||||
p = getprime(nbits)
|
||||
while True:
|
||||
q = getprime(nbits)
|
||||
if not q == p: break
|
||||
|
||||
return (p, q)
|
||||
|
||||
def extended_euclid_gcd(a, b):
|
||||
"""Returns a tuple (d, i, j) such that d = gcd(a, b) = ia + jb
|
||||
"""
|
||||
|
||||
if b == 0:
|
||||
return (a, 1, 0)
|
||||
|
||||
q = abs(a % b)
|
||||
r = long(a / b)
|
||||
(d, k, l) = extended_euclid_gcd(b, q)
|
||||
|
||||
return (d, l, k - l*r)
|
||||
|
||||
# Main function: calculate encryption and decryption keys
|
||||
def calculate_keys(p, q, nbits):
|
||||
"""Calculates an encryption and a decryption key for p and q, and
|
||||
returns them as a tuple (e, d)"""
|
||||
|
||||
n = p * q
|
||||
phi_n = (p-1) * (q-1)
|
||||
|
||||
while True:
|
||||
# Make sure e has enough bits so we ensure "wrapping" through
|
||||
# modulo n
|
||||
e = getprime(max(8, nbits/2))
|
||||
if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break
|
||||
|
||||
(d, i, j) = extended_euclid_gcd(e, phi_n)
|
||||
|
||||
if not d == 1:
|
||||
raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n))
|
||||
|
||||
if not (e * i) % phi_n == 1:
|
||||
raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n))
|
||||
|
||||
return (e, i)
|
||||
|
||||
|
||||
def gen_keys(nbits):
|
||||
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
|
||||
|
||||
Note: this can take a long time, depending on the key size.
|
||||
"""
|
||||
|
||||
while True:
|
||||
(p, q) = find_p_q(nbits)
|
||||
(e, d) = calculate_keys(p, q, nbits)
|
||||
|
||||
# For some reason, d is sometimes negative. We don't know how
|
||||
# to fix it (yet), so we keep trying until everything is shiny
|
||||
if d > 0: break
|
||||
|
||||
return (p, q, e, d)
|
||||
|
||||
def gen_pubpriv_keys(nbits):
|
||||
"""Generates public and private keys, and returns them as (pub,
|
||||
priv).
|
||||
|
||||
The public key consists of a dict {e: ..., , n: ....). The private
|
||||
key consists of a dict {d: ...., p: ...., q: ....).
|
||||
"""
|
||||
|
||||
(p, q, e, d) = gen_keys(nbits)
|
||||
|
||||
return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} )
|
||||
|
||||
def encrypt_int(message, ekey, n):
|
||||
"""Encrypts a message using encryption key 'ekey', working modulo
|
||||
n"""
|
||||
|
||||
if type(message) is types.IntType:
|
||||
return encrypt_int(long(message), ekey, n)
|
||||
|
||||
if not type(message) is types.LongType:
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
if message > 0 and \
|
||||
math.floor(math.log(message, 2)) > math.floor(math.log(n, 2)):
|
||||
raise OverflowError("The message is too long")
|
||||
|
||||
return fast_exponentiation(message, ekey, n)
|
||||
|
||||
def decrypt_int(cyphertext, dkey, n):
|
||||
"""Decrypts a cypher text using the decryption key 'dkey', working
|
||||
modulo n"""
|
||||
|
||||
return encrypt_int(cyphertext, dkey, n)
|
||||
|
||||
def sign_int(message, dkey, n):
|
||||
"""Signs 'message' using key 'dkey', working modulo n"""
|
||||
|
||||
return decrypt_int(message, dkey, n)
|
||||
|
||||
def verify_int(signed, ekey, n):
|
||||
"""verifies 'signed' using key 'ekey', working modulo n"""
|
||||
|
||||
return encrypt_int(signed, ekey, n)
|
||||
|
||||
def picklechops(chops):
|
||||
"""Pickles and base64encodes it's argument chops"""
|
||||
|
||||
value = zlib.compress(dumps(chops))
|
||||
encoded = base64.encodestring(value)
|
||||
return encoded.strip()
|
||||
|
||||
def unpicklechops(string):
|
||||
"""base64decodes and unpickes it's argument string into chops"""
|
||||
|
||||
return loads(zlib.decompress(base64.decodestring(string)))
|
||||
|
||||
def chopstring(message, key, n, funcref):
|
||||
"""Splits 'message' into chops that are at most as long as n,
|
||||
converts these into integers, and calls funcref(integer, key, n)
|
||||
for each chop.
|
||||
|
||||
Used by 'encrypt' and 'sign'.
|
||||
"""
|
||||
|
||||
msglen = len(message)
|
||||
mbits = msglen * 8
|
||||
nbits = int(math.floor(math.log(n, 2)))
|
||||
nbytes = nbits / 8
|
||||
blocks = msglen / nbytes
|
||||
|
||||
if msglen % nbytes > 0:
|
||||
blocks += 1
|
||||
|
||||
cypher = []
|
||||
|
||||
for bindex in range(blocks):
|
||||
offset = bindex * nbytes
|
||||
block = message[offset:offset+nbytes]
|
||||
value = bytes2int(block)
|
||||
cypher.append(funcref(value, key, n))
|
||||
|
||||
return picklechops(cypher)
|
||||
|
||||
def gluechops(chops, key, n, funcref):
|
||||
"""Glues chops back together into a string. calls
|
||||
funcref(integer, key, n) for each chop.
|
||||
|
||||
Used by 'decrypt' and 'verify'.
|
||||
"""
|
||||
message = ""
|
||||
|
||||
chops = unpicklechops(chops)
|
||||
|
||||
for cpart in chops:
|
||||
mpart = funcref(cpart, key, n)
|
||||
message += int2bytes(mpart)
|
||||
|
||||
return message
|
||||
|
||||
def encrypt(message, key):
|
||||
"""Encrypts a string 'message' with the public key 'key'"""
|
||||
|
||||
return chopstring(message, key['e'], key['n'], encrypt_int)
|
||||
|
||||
def sign(message, key):
|
||||
"""Signs a string 'message' with the private key 'key'"""
|
||||
|
||||
return chopstring(message, key['d'], key['p']*key['q'], decrypt_int)
|
||||
|
||||
def decrypt(cypher, key):
|
||||
"""Decrypts a cypher with the private key 'key'"""
|
||||
|
||||
return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int)
|
||||
|
||||
def verify(cypher, key):
|
||||
"""Verifies a cypher with the public key 'key'"""
|
||||
|
||||
return gluechops(cypher, key['e'], key['n'], encrypt_int)
|
||||
|
||||
# Do doctest if we're not imported
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
__all__ = ["gen_pubpriv_keys", "encrypt", "decrypt", "sign", "verify"]
|
||||
|
|
@ -1,529 +0,0 @@
|
|||
"""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.
|
||||
|
||||
"""
|
||||
|
||||
__author__ = "Sybren Stuvel, Marloes de Boer, Ivo Tamboer, and Barry Mead"
|
||||
__date__ = "2010-02-08"
|
||||
__version__ = '2.0'
|
||||
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import types
|
||||
from rsa._compat import byte
|
||||
|
||||
# Display a warning that this insecure version is imported.
|
||||
import warnings
|
||||
warnings.warn('Insecure version of the RSA module is imported as %s' % __name__)
|
||||
|
||||
|
||||
def bit_size(number):
|
||||
"""Returns the number of bits required to hold a specific long number"""
|
||||
|
||||
return int(math.ceil(math.log(number,2)))
|
||||
|
||||
def gcd(p, q):
|
||||
"""Returns the greatest common divisor of p and q
|
||||
>>> gcd(48, 180)
|
||||
12
|
||||
"""
|
||||
# Iterateive Version is faster and uses much less stack space
|
||||
while q != 0:
|
||||
if p < q: (p,q) = (q,p)
|
||||
(p,q) = (q, p % q)
|
||||
return p
|
||||
|
||||
|
||||
def bytes2int(bytes):
|
||||
"""Converts a list of bytes or a string to an integer
|
||||
|
||||
>>> (((128 * 256) + 64) * 256) + 15
|
||||
8405007
|
||||
>>> l = [128, 64, 15]
|
||||
>>> bytes2int(l) #same as bytes2int('\x80@\x0f')
|
||||
8405007
|
||||
"""
|
||||
|
||||
if not (type(bytes) is types.ListType or type(bytes) is types.StringType):
|
||||
raise TypeError("You must pass a string or a list")
|
||||
|
||||
# Convert byte stream to integer
|
||||
integer = 0
|
||||
for byte in bytes:
|
||||
integer *= 256
|
||||
if type(byte) is types.StringType: byte = ord(byte)
|
||||
integer += byte
|
||||
|
||||
return integer
|
||||
|
||||
def int2bytes(number):
|
||||
"""
|
||||
Converts a number to a string of bytes
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
string = ""
|
||||
|
||||
while number > 0:
|
||||
string = "%s%s" % (byte(number & 0xFF), string)
|
||||
number /= 256
|
||||
|
||||
return string
|
||||
|
||||
def to64(number):
|
||||
"""Converts a number in the range of 0 to 63 into base 64 digit
|
||||
character in the range of '0'-'9', 'A'-'Z', 'a'-'z','-','_'.
|
||||
|
||||
>>> to64(10)
|
||||
'A'
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
if 0 <= number <= 9: #00-09 translates to '0' - '9'
|
||||
return byte(number + 48)
|
||||
|
||||
if 10 <= number <= 35:
|
||||
return byte(number + 55) #10-35 translates to 'A' - 'Z'
|
||||
|
||||
if 36 <= number <= 61:
|
||||
return byte(number + 61) #36-61 translates to 'a' - 'z'
|
||||
|
||||
if number == 62: # 62 translates to '-' (minus)
|
||||
return byte(45)
|
||||
|
||||
if number == 63: # 63 translates to '_' (underscore)
|
||||
return byte(95)
|
||||
|
||||
raise ValueError('Invalid Base64 value: %i' % number)
|
||||
|
||||
|
||||
def from64(number):
|
||||
"""Converts an ordinal character value in the range of
|
||||
0-9,A-Z,a-z,-,_ to a number in the range of 0-63.
|
||||
|
||||
>>> from64(49)
|
||||
1
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
if 48 <= number <= 57: #ord('0') - ord('9') translates to 0-9
|
||||
return(number - 48)
|
||||
|
||||
if 65 <= number <= 90: #ord('A') - ord('Z') translates to 10-35
|
||||
return(number - 55)
|
||||
|
||||
if 97 <= number <= 122: #ord('a') - ord('z') translates to 36-61
|
||||
return(number - 61)
|
||||
|
||||
if number == 45: #ord('-') translates to 62
|
||||
return(62)
|
||||
|
||||
if number == 95: #ord('_') translates to 63
|
||||
return(63)
|
||||
|
||||
raise ValueError('Invalid Base64 value: %i' % number)
|
||||
|
||||
|
||||
def int2str64(number):
|
||||
"""Converts a number to a string of base64 encoded characters in
|
||||
the range of '0'-'9','A'-'Z,'a'-'z','-','_'.
|
||||
|
||||
>>> int2str64(123456789)
|
||||
'7MyqL'
|
||||
"""
|
||||
|
||||
if not (type(number) is types.LongType or type(number) is types.IntType):
|
||||
raise TypeError("You must pass a long or an int")
|
||||
|
||||
string = ""
|
||||
|
||||
while number > 0:
|
||||
string = "%s%s" % (to64(number & 0x3F), string)
|
||||
number /= 64
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def str642int(string):
|
||||
"""Converts a base64 encoded string into an integer.
|
||||
The chars of this string in in the range '0'-'9','A'-'Z','a'-'z','-','_'
|
||||
|
||||
>>> str642int('7MyqL')
|
||||
123456789
|
||||
"""
|
||||
|
||||
if not (type(string) is types.ListType or type(string) is types.StringType):
|
||||
raise TypeError("You must pass a string or a list")
|
||||
|
||||
integer = 0
|
||||
for byte in string:
|
||||
integer *= 64
|
||||
if type(byte) is types.StringType: byte = ord(byte)
|
||||
integer += from64(byte)
|
||||
|
||||
return integer
|
||||
|
||||
def read_random_int(nbits):
|
||||
"""Reads a random integer of approximately nbits bits rounded up
|
||||
to whole bytes"""
|
||||
|
||||
nbytes = int(math.ceil(nbits/8.))
|
||||
randomdata = os.urandom(nbytes)
|
||||
return bytes2int(randomdata)
|
||||
|
||||
def randint(minvalue, maxvalue):
|
||||
"""Returns a random integer x with minvalue <= x <= maxvalue"""
|
||||
|
||||
# Safety - get a lot of random data even if the range is fairly
|
||||
# small
|
||||
min_nbits = 32
|
||||
|
||||
# The range of the random numbers we need to generate
|
||||
range = (maxvalue - minvalue) + 1
|
||||
|
||||
# Which is this number of bytes
|
||||
rangebytes = ((bit_size(range) + 7) / 8)
|
||||
|
||||
# Convert to bits, but make sure it's always at least min_nbits*2
|
||||
rangebits = max(rangebytes * 8, min_nbits * 2)
|
||||
|
||||
# Take a random number of bits between min_nbits and rangebits
|
||||
nbits = random.randint(min_nbits, rangebits)
|
||||
|
||||
return (read_random_int(nbits) % range) + minvalue
|
||||
|
||||
def jacobi(a, b):
|
||||
"""Calculates the value of the Jacobi symbol (a/b)
|
||||
where both a and b are positive integers, and b is odd
|
||||
"""
|
||||
|
||||
if a == 0: return 0
|
||||
result = 1
|
||||
while a > 1:
|
||||
if a & 1:
|
||||
if ((a-1)*(b-1) >> 2) & 1:
|
||||
result = -result
|
||||
a, b = b % a, a
|
||||
else:
|
||||
if (((b * b) - 1) >> 3) & 1:
|
||||
result = -result
|
||||
a >>= 1
|
||||
if a == 0: return 0
|
||||
return result
|
||||
|
||||
def jacobi_witness(x, n):
|
||||
"""Returns False if n is an Euler pseudo-prime with base x, and
|
||||
True otherwise.
|
||||
"""
|
||||
|
||||
j = jacobi(x, n) % n
|
||||
f = pow(x, (n-1)/2, n)
|
||||
|
||||
if j == f: return False
|
||||
return True
|
||||
|
||||
def randomized_primality_testing(n, k):
|
||||
"""Calculates whether n is composite (which is always correct) or
|
||||
prime (which is incorrect with error probability 2**-k)
|
||||
|
||||
Returns False if the number is composite, and True if it's
|
||||
probably prime.
|
||||
"""
|
||||
|
||||
# 50% of Jacobi-witnesses can report compositness of non-prime numbers
|
||||
|
||||
for i in range(k):
|
||||
x = randint(1, n-1)
|
||||
if jacobi_witness(x, n): return False
|
||||
|
||||
return True
|
||||
|
||||
def is_prime(number):
|
||||
"""Returns True if the number is prime, and False otherwise.
|
||||
|
||||
>>> is_prime(42)
|
||||
0
|
||||
>>> is_prime(41)
|
||||
1
|
||||
"""
|
||||
|
||||
if randomized_primality_testing(number, 6):
|
||||
# Prime, according to Jacobi
|
||||
return True
|
||||
|
||||
# Not prime
|
||||
return False
|
||||
|
||||
|
||||
def getprime(nbits):
|
||||
"""Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In
|
||||
other words: nbits is rounded up to whole bytes.
|
||||
|
||||
>>> p = getprime(8)
|
||||
>>> is_prime(p-1)
|
||||
0
|
||||
>>> is_prime(p)
|
||||
1
|
||||
>>> is_prime(p+1)
|
||||
0
|
||||
"""
|
||||
|
||||
while True:
|
||||
integer = read_random_int(nbits)
|
||||
|
||||
# Make sure it's odd
|
||||
integer |= 1
|
||||
|
||||
# Test for primeness
|
||||
if is_prime(integer): break
|
||||
|
||||
# Retry if not prime
|
||||
|
||||
return integer
|
||||
|
||||
def are_relatively_prime(a, b):
|
||||
"""Returns True if a and b are relatively prime, and False if they
|
||||
are not.
|
||||
|
||||
>>> are_relatively_prime(2, 3)
|
||||
1
|
||||
>>> are_relatively_prime(2, 4)
|
||||
0
|
||||
"""
|
||||
|
||||
d = gcd(a, b)
|
||||
return (d == 1)
|
||||
|
||||
def find_p_q(nbits):
|
||||
"""Returns a tuple of two different primes of nbits bits"""
|
||||
pbits = nbits + (nbits/16) #Make sure that p and q aren't too close
|
||||
qbits = nbits - (nbits/16) #or the factoring programs can factor n
|
||||
p = getprime(pbits)
|
||||
while True:
|
||||
q = getprime(qbits)
|
||||
#Make sure p and q are different.
|
||||
if not q == p: break
|
||||
return (p, q)
|
||||
|
||||
def extended_gcd(a, b):
|
||||
"""Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb
|
||||
"""
|
||||
# r = gcd(a,b) i = multiplicitive inverse of a mod b
|
||||
# or j = multiplicitive inverse of b mod a
|
||||
# Neg return values for i or j are made positive mod b or a respectively
|
||||
# Iterateive Version is faster and uses much less stack space
|
||||
x = 0
|
||||
y = 1
|
||||
lx = 1
|
||||
ly = 0
|
||||
oa = a #Remember original a/b to remove
|
||||
ob = b #negative values from return results
|
||||
while b != 0:
|
||||
q = long(a/b)
|
||||
(a, b) = (b, a % b)
|
||||
(x, lx) = ((lx - (q * x)),x)
|
||||
(y, ly) = ((ly - (q * y)),y)
|
||||
if (lx < 0): lx += ob #If neg wrap modulo orignal b
|
||||
if (ly < 0): ly += oa #If neg wrap modulo orignal a
|
||||
return (a, lx, ly) #Return only positive values
|
||||
|
||||
# Main function: calculate encryption and decryption keys
|
||||
def calculate_keys(p, q, nbits):
|
||||
"""Calculates an encryption and a decryption key for p and q, and
|
||||
returns them as a tuple (e, d)"""
|
||||
|
||||
n = p * q
|
||||
phi_n = (p-1) * (q-1)
|
||||
|
||||
while True:
|
||||
# Make sure e has enough bits so we ensure "wrapping" through
|
||||
# modulo n
|
||||
e = max(65537,getprime(nbits/4))
|
||||
if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break
|
||||
|
||||
(d, i, j) = extended_gcd(e, phi_n)
|
||||
|
||||
if not d == 1:
|
||||
raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n))
|
||||
if (i < 0):
|
||||
raise Exception("New extended_gcd shouldn't return negative values")
|
||||
if not (e * i) % phi_n == 1:
|
||||
raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n))
|
||||
|
||||
return (e, i)
|
||||
|
||||
|
||||
def gen_keys(nbits):
|
||||
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
|
||||
|
||||
Note: this can take a long time, depending on the key size.
|
||||
"""
|
||||
|
||||
(p, q) = find_p_q(nbits)
|
||||
(e, d) = calculate_keys(p, q, nbits)
|
||||
|
||||
return (p, q, e, d)
|
||||
|
||||
def newkeys(nbits):
|
||||
"""Generates public and private keys, and returns them as (pub,
|
||||
priv).
|
||||
|
||||
The public key consists of a dict {e: ..., , n: ....). The private
|
||||
key consists of a dict {d: ...., p: ...., q: ....).
|
||||
"""
|
||||
nbits = max(9,nbits) # Don't let nbits go below 9 bits
|
||||
(p, q, e, d) = gen_keys(nbits)
|
||||
|
||||
return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} )
|
||||
|
||||
def encrypt_int(message, ekey, n):
|
||||
"""Encrypts a message using encryption key 'ekey', working modulo n"""
|
||||
|
||||
if type(message) is types.IntType:
|
||||
message = long(message)
|
||||
|
||||
if not type(message) is types.LongType:
|
||||
raise TypeError("You must pass a long or int")
|
||||
|
||||
if message < 0 or message > n:
|
||||
raise OverflowError("The message is too long")
|
||||
|
||||
#Note: Bit exponents start at zero (bit counts start at 1) this is correct
|
||||
safebit = bit_size(n) - 2 #compute safe bit (MSB - 1)
|
||||
message += (1 << safebit) #add safebit to ensure folding
|
||||
|
||||
return pow(message, ekey, n)
|
||||
|
||||
def decrypt_int(cyphertext, dkey, n):
|
||||
"""Decrypts a cypher text using the decryption key 'dkey', working
|
||||
modulo n"""
|
||||
|
||||
message = pow(cyphertext, dkey, n)
|
||||
|
||||
safebit = bit_size(n) - 2 #compute safe bit (MSB - 1)
|
||||
message -= (1 << safebit) #remove safebit before decode
|
||||
|
||||
return message
|
||||
|
||||
def encode64chops(chops):
|
||||
"""base64encodes chops and combines them into a ',' delimited string"""
|
||||
|
||||
chips = [] #chips are character chops
|
||||
|
||||
for value in chops:
|
||||
chips.append(int2str64(value))
|
||||
|
||||
#delimit chops with comma
|
||||
encoded = ','.join(chips)
|
||||
|
||||
return encoded
|
||||
|
||||
def decode64chops(string):
|
||||
"""base64decodes and makes a ',' delimited string into chops"""
|
||||
|
||||
chips = string.split(',') #split chops at commas
|
||||
|
||||
chops = []
|
||||
|
||||
for string in chips: #make char chops (chips) into chops
|
||||
chops.append(str642int(string))
|
||||
|
||||
return chops
|
||||
|
||||
def chopstring(message, key, n, funcref):
|
||||
"""Chops the 'message' into integers that fit into n,
|
||||
leaving room for a safebit to be added to ensure that all
|
||||
messages fold during exponentiation. The MSB of the number n
|
||||
is not independant modulo n (setting it could cause overflow), so
|
||||
use the next lower bit for the safebit. Therefore reserve 2-bits
|
||||
in the number n for non-data bits. Calls specified encryption
|
||||
function for each chop.
|
||||
|
||||
Used by 'encrypt' and 'sign'.
|
||||
"""
|
||||
|
||||
msglen = len(message)
|
||||
mbits = msglen * 8
|
||||
#Set aside 2-bits so setting of safebit won't overflow modulo n.
|
||||
nbits = bit_size(n) - 2 # leave room for safebit
|
||||
nbytes = nbits / 8
|
||||
blocks = msglen / nbytes
|
||||
|
||||
if msglen % nbytes > 0:
|
||||
blocks += 1
|
||||
|
||||
cypher = []
|
||||
|
||||
for bindex in range(blocks):
|
||||
offset = bindex * nbytes
|
||||
block = message[offset:offset+nbytes]
|
||||
value = bytes2int(block)
|
||||
cypher.append(funcref(value, key, n))
|
||||
|
||||
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'.
|
||||
"""
|
||||
message = ""
|
||||
|
||||
chops = decode64chops(string) #Decode base64 strings into integer chops
|
||||
|
||||
for cpart in chops:
|
||||
mpart = funcref(cpart, key, n) #Decrypt each chop
|
||||
message += int2bytes(mpart) #Combine decrypted strings into a msg
|
||||
|
||||
return message
|
||||
|
||||
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 not imported
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify"]
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
# -*- 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.
|
||||
|
||||
'''Large file support
|
||||
|
||||
- break a file into smaller blocks, and encrypt them, and store the
|
||||
encrypted blocks in another file.
|
||||
|
||||
- take such an encrypted files, decrypt its blocks, and reconstruct the
|
||||
original file.
|
||||
|
||||
The encrypted file format is as follows, where || denotes byte concatenation:
|
||||
|
||||
FILE := VERSION || BLOCK || BLOCK ...
|
||||
|
||||
BLOCK := LENGTH || DATA
|
||||
|
||||
LENGTH := varint-encoded length of the subsequent data. Varint comes from
|
||||
Google Protobuf, and encodes an integer into a variable number of bytes.
|
||||
Each byte uses the 7 lowest bits to encode the value. The highest bit set
|
||||
to 1 indicates the next byte is also part of the varint. The last byte will
|
||||
have this bit set to 0.
|
||||
|
||||
This file format is called the VARBLOCK format, in line with the varint format
|
||||
used to denote the block sizes.
|
||||
|
||||
'''
|
||||
|
||||
from rsa import key, common, pkcs1, varblock
|
||||
from rsa._compat import byte
|
||||
|
||||
def encrypt_bigfile(infile, outfile, pub_key):
|
||||
'''Encrypts a file, writing it to 'outfile' in VARBLOCK format.
|
||||
|
||||
:param infile: file-like object to read the cleartext from
|
||||
:param outfile: file-like object to write the crypto in VARBLOCK format to
|
||||
:param pub_key: :py:class:`rsa.PublicKey` to encrypt with
|
||||
|
||||
'''
|
||||
|
||||
if not isinstance(pub_key, key.PublicKey):
|
||||
raise TypeError('Public key required, but got %r' % pub_key)
|
||||
|
||||
key_bytes = common.bit_size(pub_key.n) // 8
|
||||
blocksize = key_bytes - 11 # keep space for PKCS#1 padding
|
||||
|
||||
# Write the version number to the VARBLOCK file
|
||||
outfile.write(byte(varblock.VARBLOCK_VERSION))
|
||||
|
||||
# Encrypt and write each block
|
||||
for block in varblock.yield_fixedblocks(infile, blocksize):
|
||||
crypto = pkcs1.encrypt(block, pub_key)
|
||||
|
||||
varblock.write_varint(outfile, len(crypto))
|
||||
outfile.write(crypto)
|
||||
|
||||
def decrypt_bigfile(infile, outfile, priv_key):
|
||||
'''Decrypts an encrypted VARBLOCK file, writing it to 'outfile'
|
||||
|
||||
:param infile: file-like object to read the crypto in VARBLOCK format from
|
||||
:param outfile: file-like object to write the cleartext to
|
||||
:param priv_key: :py:class:`rsa.PrivateKey` to decrypt with
|
||||
|
||||
'''
|
||||
|
||||
if not isinstance(priv_key, key.PrivateKey):
|
||||
raise TypeError('Private key required, but got %r' % priv_key)
|
||||
|
||||
for block in varblock.yield_varblocks(infile):
|
||||
cleartext = pkcs1.decrypt(block, priv_key)
|
||||
outfile.write(cleartext)
|
||||
|
||||
__all__ = ['encrypt_bigfile', 'decrypt_bigfile']
|
||||
|
379
rsa/cli.py
379
rsa/cli.py
|
@ -1,379 +0,0 @@
|
|||
# -*- 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.
|
||||
|
||||
'''Commandline scripts.
|
||||
|
||||
These scripts are called by the executables defined in setup.py.
|
||||
'''
|
||||
|
||||
from __future__ import with_statement, print_function
|
||||
|
||||
import abc
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
import rsa
|
||||
import rsa.bigfile
|
||||
import rsa.pkcs1
|
||||
|
||||
HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
|
||||
|
||||
def keygen():
|
||||
'''Key generator.'''
|
||||
|
||||
# Parse the CLI options
|
||||
parser = OptionParser(usage='usage: %prog [options] keysize',
|
||||
description='Generates a new RSA keypair of "keysize" bits.')
|
||||
|
||||
parser.add_option('--pubout', type='string',
|
||||
help='Output filename for the public key. The public key is '
|
||||
'not saved if this option is not present. You can use '
|
||||
'pyrsa-priv2pub to create the public key file later.')
|
||||
|
||||
parser.add_option('-o', '--out', type='string',
|
||||
help='Output filename for the private key. The key is '
|
||||
'written to stdout if this option is not present.')
|
||||
|
||||
parser.add_option('--form',
|
||||
help='key format of the private and public keys - default PEM',
|
||||
choices=('PEM', 'DER'), default='PEM')
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if len(cli_args) != 1:
|
||||
parser.print_help()
|
||||
raise SystemExit(1)
|
||||
|
||||
try:
|
||||
keysize = int(cli_args[0])
|
||||
except ValueError:
|
||||
parser.print_help()
|
||||
print('Not a valid number: %s' % cli_args[0], file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
print('Generating %i-bit key' % keysize, file=sys.stderr)
|
||||
(pub_key, priv_key) = rsa.newkeys(keysize)
|
||||
|
||||
|
||||
# Save public key
|
||||
if cli.pubout:
|
||||
print('Writing public key to %s' % cli.pubout, file=sys.stderr)
|
||||
data = pub_key.save_pkcs1(format=cli.form)
|
||||
with open(cli.pubout, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
|
||||
# Save private key
|
||||
data = priv_key.save_pkcs1(format=cli.form)
|
||||
|
||||
if cli.out:
|
||||
print('Writing private key to %s' % cli.out, file=sys.stderr)
|
||||
with open(cli.out, 'wb') as outfile:
|
||||
outfile.write(data)
|
||||
else:
|
||||
print('Writing private key to stdout', file=sys.stderr)
|
||||
sys.stdout.write(data)
|
||||
|
||||
|
||||
class CryptoOperation(object):
|
||||
'''CLI callable that operates with input, output, and a key.'''
|
||||
|
||||
__metaclass__ = abc.ABCMeta
|
||||
|
||||
keyname = 'public' # or 'private'
|
||||
usage = 'usage: %%prog [options] %(keyname)s_key'
|
||||
description = None
|
||||
operation = 'decrypt'
|
||||
operation_past = 'decrypted'
|
||||
operation_progressive = 'decrypting'
|
||||
input_help = 'Name of the file to %(operation)s. Reads from stdin if ' \
|
||||
'not specified.'
|
||||
output_help = 'Name of the file to write the %(operation_past)s file ' \
|
||||
'to. Written to stdout if this option is not present.'
|
||||
expected_cli_args = 1
|
||||
has_output = True
|
||||
|
||||
key_class = rsa.PublicKey
|
||||
|
||||
def __init__(self):
|
||||
self.usage = self.usage % self.__class__.__dict__
|
||||
self.input_help = self.input_help % self.__class__.__dict__
|
||||
self.output_help = self.output_help % self.__class__.__dict__
|
||||
|
||||
@abc.abstractmethod
|
||||
def perform_operation(self, indata, key, cli_args=None):
|
||||
'''Performs the program's operation.
|
||||
|
||||
Implement in a subclass.
|
||||
|
||||
:returns: the data to write to the output.
|
||||
'''
|
||||
|
||||
def __call__(self):
|
||||
'''Runs the program.'''
|
||||
|
||||
(cli, cli_args) = self.parse_cli()
|
||||
|
||||
key = self.read_key(cli_args[0], cli.keyform)
|
||||
|
||||
indata = self.read_infile(cli.input)
|
||||
|
||||
print(self.operation_progressive.title(), file=sys.stderr)
|
||||
outdata = self.perform_operation(indata, key, cli_args)
|
||||
|
||||
if self.has_output:
|
||||
self.write_outfile(outdata, cli.output)
|
||||
|
||||
def parse_cli(self):
|
||||
'''Parse the CLI options
|
||||
|
||||
:returns: (cli_opts, cli_args)
|
||||
'''
|
||||
|
||||
parser = OptionParser(usage=self.usage, description=self.description)
|
||||
|
||||
parser.add_option('-i', '--input', type='string', help=self.input_help)
|
||||
|
||||
if self.has_output:
|
||||
parser.add_option('-o', '--output', type='string', help=self.output_help)
|
||||
|
||||
parser.add_option('--keyform',
|
||||
help='Key format of the %s key - default PEM' % self.keyname,
|
||||
choices=('PEM', 'DER'), default='PEM')
|
||||
|
||||
(cli, cli_args) = parser.parse_args(sys.argv[1:])
|
||||
|
||||
if len(cli_args) != self.expected_cli_args:
|
||||
parser.print_help()
|
||||
raise SystemExit(1)
|
||||
|
||||
return (cli, cli_args)
|
||||
|
||||
def read_key(self, filename, keyform):
|
||||
'''Reads a public or private key.'''
|
||||
|
||||
print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr)
|
||||
with open(filename, 'rb') as keyfile:
|
||||
keydata = keyfile.read()
|
||||
|
||||
return self.key_class.load_pkcs1(keydata, keyform)
|
||||
|
||||
def read_infile(self, inname):
|
||||
'''Read the input file'''
|
||||
|
||||
if inname:
|
||||
print('Reading input from %s' % inname, file=sys.stderr)
|
||||
with open(inname, 'rb') as infile:
|
||||
return infile.read()
|
||||
|
||||
print('Reading input from stdin', file=sys.stderr)
|
||||
return sys.stdin.read()
|
||||
|
||||
def write_outfile(self, outdata, outname):
|
||||
'''Write the output file'''
|
||||
|
||||
if outname:
|
||||
print('Writing output to %s' % outname, file=sys.stderr)
|
||||
with open(outname, 'wb') as outfile:
|
||||
outfile.write(outdata)
|
||||
else:
|
||||
print('Writing output to stdout', file=sys.stderr)
|
||||
sys.stdout.write(outdata)
|
||||
|
||||
class EncryptOperation(CryptoOperation):
|
||||
'''Encrypts a file.'''
|
||||
|
||||
keyname = 'public'
|
||||
description = ('Encrypts a file. The file must be shorter than the key '
|
||||
'length in order to be encrypted. For larger files, use the '
|
||||
'pyrsa-encrypt-bigfile command.')
|
||||
operation = 'encrypt'
|
||||
operation_past = 'encrypted'
|
||||
operation_progressive = 'encrypting'
|
||||
|
||||
|
||||
def perform_operation(self, indata, pub_key, cli_args=None):
|
||||
'''Encrypts files.'''
|
||||
|
||||
return rsa.encrypt(indata, pub_key)
|
||||
|
||||
class DecryptOperation(CryptoOperation):
|
||||
'''Decrypts a file.'''
|
||||
|
||||
keyname = 'private'
|
||||
description = ('Decrypts a file. The original file must be shorter than '
|
||||
'the key length in order to have been encrypted. For larger '
|
||||
'files, use the pyrsa-decrypt-bigfile command.')
|
||||
operation = 'decrypt'
|
||||
operation_past = 'decrypted'
|
||||
operation_progressive = 'decrypting'
|
||||
key_class = rsa.PrivateKey
|
||||
|
||||
def perform_operation(self, indata, priv_key, cli_args= |