0

I have two sides:

  • one or more clients, running Python 2.7.10, Pycrypto 2.6.1
  • one server, running the same
  • all running on: django 1.11.2, Centos 6.9, OpenSSL 1.0.1e-fips

  • the server holds an HSM-backed private key and the corresponding public key.

  • the clients have the public key at their disposal, and use it to encrypt before sending the encrypted data to the server.

Client-side code to encrypt:

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Hash import SHA256


def encrypt_RSA(public_key_loc, clear_text):
    key = open(public_key_loc, "r").read()
    rsakey = RSA.importKey(key)
    cipher = PKCS1_OAEP.new(rsakey, hashAlgo=SHA256)
    encrypted = cipher.encrypt(clear_text)
    return encrypted.encode('base64')

Client-side code to send:

import json
import requests
from .crypt import encrypt_RSA

r = requests.post(
    settings.THE_URL,
    data=json.dumps({
        'text': encrypt_RSA(settings.PUBLIC_KEY, 'clear text'),
    }),
    headers={'content-type': 'application/json'},
    timeout=5
)

This works -- apparently.

When received server-side, the following happens:

engine "chil" set.
RSA operation error
140648313706312:error:0407106B:rsa routines:RSA_padding_check_PKCS1_type_2:block type is not 02:rsa_pk1.c:190:
140648313706312:error:04065072:rsa routines:RSA_EAY_PRIVATE_DECRYPT:padding check failed:rsa_eay.c:674:

This is the code handling decryption, after going through a layer of validators such as django REST framework.

def decrypt_RSA_CHIL(private_key_loc, encrypted_text):

    print private_key_loc

    print '######### BEFORE ##########'
    print encrypted_text
    encrypted_text = base64.b64decode(encrypted_text)
    print '######### AFTER ##########'
    print encrypted_text

    # after checking those temp files, they have a proper size of 256 byte
    # so it seems they are indeed padded
    encrypted_text_location = '/tmp/encrypted_' + str(uuid.uuid4())

    print encrypted_text_location

    f = open(encrypted_text_location, 'w')
    f.write(encrypted_text)
    f.close()

    # Running the decryption command
    result = execute(
        [
            '/opt/nfast/bin/preload /usr/bin/openssl rsautl -engine chil -decrypt -inkey '
            + str(private_key_loc) + ' -in ' + encrypted_text_location
        ],
        '/tmp'
    )
    print result

    # removing the file with encrypted data from file system
    os.remove(encrypted_text_location)

    stdout_list = result[1].split("\n") if result[1] else []

    print stdout_list

    if result[0] is False and stdout_list and result[2] == u'engine "chil" set.\n':
        return stdout_list[0]
    else:
        pass

    return None

The execute method (found somewhere on the Internet a long time ago and has worked fine so far):

import subprocess

def execute(cmd_array, working_dir):

    stdout = ''
    stderr = ''

    try:
        try:
            process = subprocess.Popen(
                cmd_array,
                cwd=working_dir,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                bufsize=1,
                shell=True,
            )
        except OSError as e:
            return [False, '', 'ERROR : command(' + ' '.join(cmd_array) + ') could not get executed!']

        for line in iter(process.stdout.readline, b''):

            try:
                echo_line = line.decode("utf-8")
            except:
                echo_line = str(line)

            stdout += echo_line

        for line in iter(process.stderr.readline, b''):

            try:
                echo_line = line.decode("utf-8")
            except:
                echo_line = str(line)

            stderr += echo_line.decode()

    except (KeyboardInterrupt, SystemExit) as err:
        return [False, '', str(err)]

    process.stdout.close()

    return_code = process.wait()
    if return_code != 0 or stderr != '':
        return [False, stdout, stderr]
    else:
        return [True, stdout, stderr]

Now I know the HSM (relatively low-end model) does work:

> sudo /opt/nfast/bin/generatekey embed name=PRIVKEYNAME cardset=Keymanager
# answer a few questions
# private key saved in /tmp/KEY

> sudo openssl rsa -in /tmp/KEY -pubout > /tmp/KEY.pub
# generates the public key

> sudo cat /tmp/clear.txt | openssl rsautl -encrypt -pubin -inkey veeip2.pub > /tmp/encrypted.txt
# "clear.txt" contains "foo"
# "encrypted.txt" is always 256 bytes long, which is correct.

> sudo /opt/nfast/bin/preload /usr/bin/openssl rsautl -engine chil -decrypt -inkey /tmp/KEY -in /tmp/encrypted.txt
# engine "chil" set.
# "foo"

There seems to be a loss (or an addition??) somewhere between the client and the server, related to padding. But for the life of me, I have no idea what it could be. I suspect the way the encryption is done client-side could be the culprit, as when done locally, everything works flawlessly.

Thanks

Hal
  • 537
  • 4
  • 13

0 Answers0