Ejemplo n.º 1
0
def write_rsa_pkcs1_private(opts: argparse.Namespace, rsa: RSAFactors) -> None:
    '''
    Write the PKCS#1 private key.
    Citation: https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem
    Citation: http://blog.oddbit.com/2011/05/08/converting-openssh-public-keys/
    Citation: https://tools.ietf.org/html/rfc3447
    Note that this wraps at 64 to match SSH.
    '''
    pkcs1_seq = univ.Sequence()
    pkcs1_seq.setComponentByPosition(0, univ.Integer(rsa.m_version))  # Version (0-std, 1-multiprime)
    pkcs1_seq.setComponentByPosition(1, univ.Integer(rsa.m_modulus))  # modulus
    pkcs1_seq.setComponentByPosition(2, univ.Integer(rsa.m_public_exponent))  # publicExponent
    pkcs1_seq.setComponentByPosition(3, univ.Integer(rsa.m_private_exponent))  # privateExponent
    pkcs1_seq.setComponentByPosition(4, univ.Integer(rsa.m_prime1))  # prime1
    pkcs1_seq.setComponentByPosition(5, univ.Integer(rsa.m_prime2))  # prime2
    pkcs1_seq.setComponentByPosition(6, univ.Integer(rsa.m_exponent1))  # exponent1
    pkcs1_seq.setComponentByPosition(7, univ.Integer(rsa.m_exponent2))  # exponent2
    pkcs1_seq.setComponentByPosition(8, univ.Integer(rsa.m_crt_coefficient))  # coefficient
    data = base64.b64encode(der_encoder.encode(pkcs1_seq))
    data_str = str(data, 'utf-8').rstrip()
    data_str = '\n'.join(textwrap.wrap(data_str, 64))
    path = opts.out
    infov(opts, f'writing {path} - PCKS#1 private')
    with open(path, 'w') as ofp:
        ofp.write(f'''\
-----BEGIN RSA PRIVATE KEY-----
{data_str}
-----END RSA PRIVATE KEY-----
''')
Ejemplo n.º 2
0
def read_input(opts: argparse.Namespace) -> Tuple[bytes, int, int]:
    '''
    Grab the bytes.
    '''
    data = read_raw_input(opts)
    if b'-----BEGIN JOES RSA ENCRYPTED DATA-----' in data:
        # This is the PEM format, grab the binary data.
        first, *b64, last = str(data, 'utf-8').strip().split('\n')
        if first != '-----BEGIN JOES RSA ENCRYPTED DATA-----':
            err('invalid encrypted format: prefix not found')
        if last != '-----END JOES RSA ENCRYPTED DATA-----':
            err('invalid encrypted format: suffix not found')
        b64_str = ''.join([o.strip() for o in b64])
        data = base64.b64decode(b64_str)

    # Check the binary fields.
    if data[:8] != b'joes-rsa':
        err(f'invalid encrypted format: expected prefix not found: {data[:8]}')
    version = struct.unpack('>H', data[8:10])[0]
    if version != 0:
        err(f'invalid encrypted format version: {version}, expected 0')
    padding = struct.unpack('>H', data[10:12])[0]

    infov(opts, f'version: {version}')
    infov(opts, f'size: {len(data[12:])}')

    return data[12:], len(data[12:]), padding
Ejemplo n.º 3
0
def read_raw_input(opts: argparse.Namespace) -> bytes:
    '''
    Read the raw ciphertext data.
    It can be either binary or PEM.
    '''
    if opts.input:
        with open(opts.input, 'rb') as ifp:
            return ifp.read()
    infov(opts, 'reading from stdin, type ^D on a new line to exit')
    return bytes(sys.stdin.read(), 'utf-8')
Ejemplo n.º 4
0
def main() -> None:
    '''
    main
    '''
    opts = getopts()
    if opts.seed:
        random.seed(opts.seed)

    modulus, pubexp = read_public_key_file(opts, opts.key)
    infovv(opts, f'modulus: 0x{modulus:x}')
    infovv(opts, f'pubexp : 0x{pubexp:x}')
    encrypt(opts, modulus, pubexp)
    infov(opts, 'done')
Ejemplo n.º 5
0
def main() -> None:
    '''
    main
    '''
    opts = getopts()
    if opts.seed:
        random.seed(opts.seed)

    rsa = read_pkcs1_prikey(opts)
    if opts.verbose > 1:
        infov(opts, 'RSA Parameters')
        dump_namedtuple(rsa)
    decrypt(opts, rsa)
    infov(opts, 'done')
Ejemplo n.º 6
0
def read_input(opts: argparse.Namespace) -> bytes:
    '''
    Read the input data.

    There are two possible sources:
       1. A file specified by the -i option.
       2. stdin.

    In both cases, all data is read into memory which
    limits the file size to available memory.
    '''
    if opts.input:
        with open(opts.input, 'rb') as ifp:
            return ifp.read()
    infov(opts, 'reading from stdin, type ^D on a new line to exit')
    return bytes(sys.stdin.read(), 'utf-8')
Ejemplo n.º 7
0
def main() -> None:
    '''
    Main entry point.
    '''
    opts = getopts()
    if opts.seed:
        random.seed(opts.seed)
    public_exponent = get_int_arg(opts.encrypt_exponent)
    if opts.primes:
        prime1 = get_int_arg(opts.primes[0])
        prime2 = get_int_arg(opts.primes[1])
    else:
        prime1 = generate_prime(opts)
        prime2 = generate_prime(opts)
    assert prime1 != prime2
    rsa = RSAFactors(prime1, prime2, public_exponent)
    write_keys(opts, rsa)
    infov(opts, 'done')
Ejemplo n.º 8
0
def write_rsa_ssh_public(opts: argparse.Namespace, rsa: RSAFactors) -> None:
    '''
    Write SSH format: https://tools.ietf.org/html/rfc4716.
    Citation: http://blog.oddbit.com/2011/05/08/converting-openssh-public-keys/
    Citation: http://blog.thedigitalcatonline.com/blog/2018/04/25/rsa-keys/

    The base64 fields are composed of 3 length/data pairs with all
    data in big endian format.
       1. Prefix (the algorithm):
             length is 7
             data is "ssh-rsa"
       2. Public (encryption) key:
             length is the number of bytes in the public key
             data is the key
       3. Modulus:
             length is the number of bytes + 1 (to guarantee unsigned)
             data is the modulus
    '''
    # Prefix.
    pre_bytes = bytes('ssh-rsa', 'utf-8')
    pre_size = len(pre_bytes).to_bytes(4, byteorder='big')

    # Public key.
    pub_num_bytes = math.ceil(math.log(rsa.m_public_exponent, 256))
    pub_bytes = rsa.m_public_exponent.to_bytes(pub_num_bytes, byteorder='big')
    pub_size = pub_num_bytes.to_bytes(4, byteorder='big')

    # Modulus.
    mod_num_bytes = 1 + math.ceil(math.log(rsa.m_modulus, 256))
    mod_bytes = rsa.m_modulus.to_bytes(mod_num_bytes, byteorder='big')
    mod_size = mod_num_bytes.to_bytes(4, byteorder='big')

    # Byte data.
    byte_array = pre_size + pre_bytes + pub_size + pub_bytes + mod_size + mod_bytes
    data = base64.b64encode(byte_array)
    data_str = str(data, 'utf-8').rstrip()

    # Write to file.
    path = opts.out + '.pub'
    infov(opts, f'writing {path} - SSH public')
    with open(path, 'w') as ofp:
        ofp.write(f'''\
ssh-rsa {data_str} {getpass.getuser()}@{socket.gethostname()}
''')
Ejemplo n.º 9
0
def write_rsa_pkcs1_pem_public(opts: argparse.Namespace, rsa: RSAFactors) -> None:
    '''
    Write PEM RSA public key: PKCS#1.
    Citation: http://blog.oddbit.com/2011/05/08/converting-openssh-public-keys/
    Note that this wraps at 64 to match SSH.
    '''
    pkcs1_seq = univ.Sequence()
    pkcs1_seq.setComponentByPosition(0, univ.Integer(rsa.m_modulus))
    pkcs1_seq.setComponentByPosition(1, univ.Integer(rsa.m_public_exponent))
    data = base64.b64encode(der_encoder.encode(pkcs1_seq))
    data_str = str(data, 'utf-8').rstrip()
    data_str = '\n'.join(textwrap.wrap(data_str, 64))
    path = opts.out + '.pub.pem'
    infov(opts, f'writing {path} - PCKS#1 public')
    with open(path, 'w') as ofp:
        ofp.write(f'''\
-----BEGIN RSA PUBLIC KEY-----
{data_str}
-----END RSA PUBLIC KEY-----
''')
Ejemplo n.º 10
0
def decrypt(opts: argparse.Namespace, rsa: namedtuple) -> None:
    '''
    Decrypt the file data.
    '''
    infov(opts, 'reading the input data')
    ciphertext, size, padding = read_input(opts)
    plaintext = bytes([])
    bytes_per_block = 1 + (math.ceil(math.log(rsa.modulus, 2)) // 8)
    infov(opts, f'bytes/block: {bytes_per_block}')
    infov(opts, f'padding: {padding}')
    for i in range(0, size, bytes_per_block):
        end = i + bytes_per_block
        block = ciphertext[i:end]
        block = bytes([0]) + block

        # Convert the block to an integer for computation.
        block_int = int.from_bytes(block, 'big')

        # Decrypt.
        block_dec_int = int(pow(block_int, rsa.priexp, rsa.modulus))

        # Added to the decrypted bytes array.
        block_bytes = block_dec_int.to_bytes(bytes_per_block, byteorder='big')
        plaintext += block_bytes[1:]  # ignore the extra byte added by encrypt

    if padding:
        plaintext = plaintext[:-padding]

    if opts.output:
        with open(opts.output, 'wb') as ofp:
            ofp.write(plaintext)
    else:
        sys.stdout.write(str(plaintext, 'utf-8'))
Ejemplo n.º 11
0
def read_pkcs1_prikey(opts: argparse.Namespace) -> namedtuple:
    '''
    Read the RSA private key file.
    '''
    infov(opts, f'reading key file {opts.key}')
    with open(opts.key, 'r') as ifp:
        first, *b64, last = ifp.readlines()
    assert first.strip() == '-----BEGIN RSA PRIVATE KEY-----'
    assert last.strip() == '-----END RSA PRIVATE KEY-----'
    b64_str = ''.join([o.strip() for o in b64])
    b64_bytes = base64.b64decode(b64_str)

    # This is decoded raw, with no structure, that is why
    # recursion is disabled.
    _, msg = der_decoder.decode(b64_bytes,
                                asn1Spec=univ.Sequence(),
                                recursiveFlag=False)
    version, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    modulus, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    pubexp, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    priexp, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    prime1, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    prime2, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    exponent1, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    exponent2, msg = der_decoder.decode(msg, asn1Spec=univ.Integer())
    crt_coeff, _ = der_decoder.decode(msg, asn1Spec=univ.Integer())
    rec = {
        'version': version,
        'modulus': int(modulus),
        'pubexp': int(pubexp),
        'priexp': int(priexp),
        'prime1': int(prime1),
        'prime2': int(prime2),
        'exponent1': int(exponent1),
        'exponent2': int(exponent2),
        'crt_coeff': int(crt_coeff),
    }
    ntdef = namedtuple('_', sorted(rec.keys()))
    return ntdef(**rec)
Ejemplo n.º 12
0
def read_public_key_file(opts: argparse.Namespace,
                         path: str) -> Tuple[int, int]:
    '''
    Figure out which type of file this is and read it.
    '''
    infov(opts, f'opening key file: {path}')
    with open(path, 'r') as ifp:
        data = ifp.read()
    if '-----BEGIN RSA PUBLIC KEY-----' in data:
        infov(opts, f'key type: PKCS#1 (RSA) PEM public key file')
        return read_pem_rsa_pubkey(path)
    if 'ssh-rsa' in data:
        infov(opts, f'key type: SSH RSA public key file')
        _, pub, mod = read_ssh_rsa_pubkey(path)
        return mod, pub
    err(f'unrecognized file format in {path}.')
    return -1, -1
Ejemplo n.º 13
0
def encrypt(opts: argparse.Namespace, modulus: int, pubexp: int) -> None:
    '''
    Encrypt the input using RSA.
    '''
    infov(opts, 'reading the input data')
    plaintext = read_input(opts)
    infov(opts, f'read {len(plaintext)} bytes')
    num_bits = int(math.ceil(math.log(modulus, 2)))
    bytes_per_block = num_bits // 8  # based on bits
    infov(opts, f'num_bits: {num_bits}')
    infov(opts, f'bytes/block: {bytes_per_block}')
    assert bytes_per_block < 0xffff  # we only allocate 2 bytes for padding

    padding = 0
    while len(plaintext) % bytes_per_block:
        padding += 1
        plaintext += b'x'
    infov(opts, f'padding: {padding}')
    assert (len(plaintext) % bytes_per_block) == 0
    ciphertext = bytes([])
    for i in range(0, len(plaintext), bytes_per_block):
        end = i + bytes_per_block
        block = plaintext[i:end]

        # Convert the block to an integer for computation.
        # Arbitrarily chose big endian because consistency is needed and
        # 'big' is fewer letters than 'little'. Also because 'big' is
        # 'network order'.
        block_int = int.from_bytes(block, 'big')

        # Encrypt.
        # Use the fast modular exponentiation algorithm provided by
        # python.
        block_enc_int = int(pow(block_int, pubexp, modulus))

        # Add to the encrypted bytes array.
        # The MSB is always zero.
        block_bytes = block_enc_int.to_bytes(bytes_per_block + 1,
                                             byteorder='big')
        ciphertext += block_bytes

    # Setup the prefix.
    version = 0
    prefix = bytes('joes-rsa', 'utf-8')
    prefix += version.to_bytes(2, 'big')
    prefix += padding.to_bytes(2, 'big')

    ciphertext = prefix + ciphertext

    # At this point the data is encrypted.
    # If the user did not specify binary output, output in base64.
    if opts.binary:
        encb = ciphertext
        encs = str(ciphertext)
        mode = 'wb'
    else:
        b64 = base64.b64encode(ciphertext)
        data_str = str(b64, 'utf-8').rstrip()
        data_str = '\n'.join(textwrap.wrap(data_str, 64))
        encs = f'''\
-----BEGIN JOES RSA ENCRYPTED DATA-----
{data_str}
-----END JOES RSA ENCRYPTED DATA-----
'''
        mode = 'w'

    # Write out the data.
    if opts.output:
        infov(opts, f'writing to {opts.output}')
        with open(opts.output, mode) as ofp:
            if opts.binary:
                ofp.write(encb)
            else:
                ofp.write(encs)
    else:
        infov(opts, 'writing to stdout')
        sys.stdout.write(encs)