ThalesGroup / pycryptoki

Python interface to SafeNet's PKCS11 library implementation
Apache License 2.0
60 stars 23 forks source link

CKM_NIST_PRF_KDF #28

Closed sinu closed 2 years ago

sinu commented 2 years ago

can you provide an example to utilize CKM_NIST_PRF_KDF template

sinu commented 2 years ago

@astraw38 @rlenferink @aderkrik @Sebastienlejeune can u help?

Sebastienlejeune commented 2 years ago

Hi @sinu I will contact the people in charge of this repo

astraw38 commented 2 years ago

Session/login/base key code can be grabbed from other examples.

    from ctypes import *
    from pycryptoki.key_generator import *
    from pycryptoki.cryptoki import *
    from pycryptoki.defines import *

    def get_prf_kdf_mech():
        mech = CK_MECHANISM()
        mech.mechanism = CKM_PRF_KDF

        params = CK_KDF_PRF_PARAMS()
        # Set PRF type (defined in pycryptoki.defines)
        params.prfType = CK_NIST_PRF_KDF_HMAC_SHA3_256
        # Select encoding scheme. Constants defined in pycryptoki.defines.
        # Number from 0-5, similar to what you have in ckdemo.
        params.ulEncodingScheme = LUNA_PRF_KDF_ENCODING_SCHEME_3

        # Set PRF label/context/counter
        params.pLabel = (c_ubyte * 8)(*list(range(8)))
        params.ulLabelLen = 8
        params.pContext = (c_ubyte * 8)(*list(range(8)))
        params.ulContextLen = 8
        params.ulCounter = 1

        # assign to mechanism parameter
        mech.pParameter = cast(pointer(params), c_void_p)
        mech.usParameterLen = sizeof(params)

        return mech

    # do c_initialize/open_session/login
    # Also create/or get base key handle
    mech = get_prf_kdf_mech()
    new_key_template = {
        CKA_LABEL: b"derived AES key",
        CKA_VALUE_LEN: 32,
        CKA_KEY_TYPE: CKK_AES,
        CKA_CLASS: CKO_SECRET_KEY,
        CKA_PRIVATE: True,
        CKA_SENSITIVE: True,
        CKA_ENCRYPT: True,
        CKA_DECRYPT: True,
        # other attrs
    }
    derived_key = c_derive_key_ex(session, h_base_key, new_key_template, mech)
    print("Derived key: %s" % derived_key)
sinu commented 2 years ago

Session/login/base key code can be grabbed from other examples.

    from ctypes import *
    from pycryptoki.key_generator import *
    from pycryptoki.cryptoki import *
    from pycryptoki.defines import *

    def get_prf_kdf_mech():
        mech = CK_MECHANISM()
        mech.mechanism = CKM_PRF_KDF

        params = CK_KDF_PRF_PARAMS()
        # Set PRF type (defined in pycryptoki.defines)
        params.prfType = CK_NIST_PRF_KDF_HMAC_SHA3_256
        # Select encoding scheme. Constants defined in pycryptoki.defines.
        # Number from 0-5, similar to what you have in ckdemo.
        params.ulEncodingScheme = LUNA_PRF_KDF_ENCODING_SCHEME_3

        # Set PRF label/context/counter
        params.pLabel = (c_ubyte * 8)(*list(range(8)))
        params.ulLabelLen = 8
        params.pContext = (c_ubyte * 8)(*list(range(8)))
        params.ulContextLen = 8
        params.ulCounter = 1

        # assign to mechanism parameter
        mech.pParameter = cast(pointer(params), c_void_p)
        mech.usParameterLen = sizeof(params)

        return mech

    # do c_initialize/open_session/login
    # Also create/or get base key handle
    mech = get_prf_kdf_mech()
    new_key_template = {
        CKA_LABEL: b"derived AES key",
        CKA_VALUE_LEN: 32,
        CKA_KEY_TYPE: CKK_AES,
        CKA_CLASS: CKO_SECRET_KEY,
        CKA_PRIVATE: True,
        CKA_SENSITIVE: True,
        CKA_ENCRYPT: True,
        CKA_DECRYPT: True,
        # other attrs
    }
    derived_key = c_derive_key_ex(session, h_base_key, new_key_template, mech)
    print("Derived key: %s" % derived_key)

@astraw38 thanks for your response , i am looking for derive key along with a data should be able to derive key from Base Key + DATA is it possible

astraw38 commented 2 years ago

As part of the CKM_PRF_KDF? Because that would be the pContext in the example (note you'd need to encode it appropriately, it's a binary string) -- see NIST SP 800-108 for details. Or as a different key derivation. Because the latter is trivial (see CKM_CONCATENATE_BASE_AND_DATA), but limited in use.

sinu commented 2 years ago

@astraw38 this is my scenario

vendor > master key (shared with mycompany) > derive key using master key and data > use derived key for encrypt file

mycompany > master key (shared by vendor > derive key using master key and data > use derived key to decrypt file

this is the logic that we are trying to implement

astraw38 commented 2 years ago

Sounds more like you're trying to do a DH (RSA or EC) key exchange, which means you'd use CKM_ECDH_DERIVE, and you can provide sharedData/publicData into the mechanism parameter.

sinu commented 2 years ago

@astraw38 thanks for your response but we need AES keys any idea how we can achieve this

astraw38 commented 2 years ago

a DH/ECDH exchange will result in key material. I highly recommend figuring out exactly what you're trying to do before proceeding. Slight errors in how you're constructing the various parts of the KEX can result in failures (derived keys not matching w/ other side of the exchange). Do you have any sort of spec that you're working off of?

sinu commented 2 years ago

@astraw38 iam getting error when i try to import cryptoki

>>> from pycryptoki.cryptoki import *
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'pycryptoki.cryptoki' has no attribute 'CK_CPV4_INSERT_PARAMS_PTR'

Any idea about this error

sinu commented 2 years ago

Hi @astraw38, I used the following code to derive the keys and it is workings as expected , is there any way to use a string for params.pContext ?

We received a sample cpp file from thales and it has this capability, please the snip

  case 3:
      mechanism.mechanism = CKM_NIST_PRF_KDF;
      mechanism.pParameter = &prfParams;
      mechanism.usParameterLen = sizeof(prfParams);
      strcpy( label, "Derived Key: AES CMAC" );

      prfParams.prfType = CK_NIST_PRF_KDF_AES_CMAC;
      prfParams.ulEncodingScheme = LUNA_PRF_KDF_ENCODING_SCHEME_1;
      prfParams.ulCounter = 1;

      printf("\nEnter Label string (30 char max): ");
      fgets(prfLabel, 32, stdin);
      prfParams.pLabel = (CK_BYTE_PTR)prfLabel;
      prfParams.ulLabelLen = strlen(prfLabel);
      printf("\nEnter Context string (30 char max): ");
      fgets(prfContext, 32, stdin);
      prfParams.pContext = (CK_BYTE_PTR)prfContext;
      prfParams.ulContextLen = strlen(prfContext);
      break;

   default:
import logging
from pycryptoki.ca_extensions.object_handler import ca_get_object_handle_ex
from pycryptoki.misc import c_create_object_ex
from pycryptoki.object_attr_lookup import c_get_attribute_value_ex
from pycryptoki.default_templates import *
from pycryptoki.defines import *
from pycryptoki.session_management import *
from pycryptoki.object_attr_lookup import c_find_objects_ex
from pycryptoki.conversions import to_bytestring, from_hex
from pycryptoki.mechanism import Mechanism
from pycryptoki.key_generator import *
from pycryptoki.encryption import *
from ctypes import *
from pycryptoki.cryptoki import *

logger = logging.getLogger(__name__)

def get_prf_kdf_mech():
    mech = CK_MECHANISM()
    mech.mechanism = CKM_NIST_PRF_KDF

    params = CK_KDF_PRF_PARAMS()
    # Set PRF type (defined in pycryptoki.defines)
    params.prfType = CK_NIST_PRF_KDF_AES_CMAC
    # Select encoding scheme. Constants defined in pycryptoki.defines.
    # Number from 0-5, similar to what you have in ckdemo.
    params.ulEncodingScheme = LUNA_PRF_KDF_ENCODING_SCHEME_1

    # Set PRF label/context/counter
    params.pLabel = (c_ubyte * 8)(*list(range(8)))
    params.ulLabelLen = 8
    params.pContext = (c_ubyte * 8)(*list(range(8)))
    params.ulContextLen = 8
    params.ulCounter = 1

    # assign to mechanism parameter
    mech.pParameter = cast(pointer(params), c_void_p)
    mech.usParameterLen = sizeof(params)

    return mech

c_initialize_ex()
auth_session = c_open_session_ex(3)   # HSM slot # in this example is 0
login_ex(auth_session, 3, 'passwd')  # 0 is still the slot number, ‘userpin’ should be replaced by your password (None if PED or no challenge)

template = {CKA_LABEL: b"IDSMASTER"}
keys = c_find_objects_ex(auth_session, template, 1)
print(keys)
h_base_key = keys.pop(0)
print(h_base_key)

mech = get_prf_kdf_mech()

Derive_key_template = get_default_key_template(CKM_AES_KEY_GEN)
Derive_key_template[CKA_LABEL] = "Derived Key From Base"
Derive_key_template[CKA_VALUE_LEN] = 32
Derive_key_template[CKA_KEY_TYPE] = CKK_AES
Derive_key_template[CKA_CLASS] = CKO_SECRET_KEY
Derive_key_template[CKA_PRIVATE] = True
Derive_key_template[CKA_SENSITIVE] = True
Derive_key_template[CKA_DERIVE] = True
Derive_key_template[CKA_EXTRACTABLE] = True

derived_key = c_derive_key_ex(auth_session, h_base_key, Derive_key_template, mech)
print("Derived key: %s" % derived_key)

c_logout_ex(auth_session)
c_close_session_ex(auth_session)
c_finalize_ex()
astraw38 commented 2 years ago

It's a CK_BYTE_PTR, so while you can read that from terminal, you would need to convert it. Python3 strings would be unicode by default, and you would need to encode it to bytes, then create the buffer:

# Read from stdin
context = input("enter context\n")

# convert to bytes -- use appropriate encoding as necessary
context_bytes = context.encode('utf-8')

# convert to ck_byte array
context_arr = create_string_buffer(context_bytes, len(context_bytes))

mech.pContext = context_arr
mech.ulContextLen = len(context_arr)

You can do it all in one line if you're confident about the encoding:

context = input("enter context\n").encode('utf-8')
mech.pContext = create_string_buffer(context, len(context)
mech.ulContextLen = len(context)
sinu commented 2 years ago

@astraw38 thanks for your support now everything working as expected, Can we export or create the derived locally ?, now the derived keys are getting generated inside the HSM?

astraw38 commented 2 years ago

Key would be created within the HSM. You can export it if you have appropriate policies set on your partition (secret key wrapping/unwrapping), and you've got attributes set on the derived key (CKR_EXTRACTABLE). If those are done, you can extract it by creating another AES key w/ CKR_DECRYPT & CKR_WRAP set to true, wrapping the derived key off the HSM, then decrypting that wrapped key.

sinu commented 2 years ago

@astraw38 we successfully exported the AES key outside the HSM in a wrapped format, But do we read the key, seems an encoded files received as export

astraw38 commented 2 years ago

AES keys are random bytes. So if you have a 32b AES key, when unwrapped & decrypted, you would have 32 bytes of random data. See the PKCS11 spec.

sinu commented 2 years ago

@astraw38 thanks for you support always

  1. derive aes symmetric key in HSM

ouid b22600000b000002f5ec0900 -- label: Derived Key From Base

  1. create a key pair inside HSM (RSA ).

    1. wrap the aes key (created in step1) with private key.

-rw-r--r-- 1 root root 40 May 26 05:51 wrapped.key

4.export the public key that was created in step2.

we tried to decrypt wrapped.key with exported public key still we are getting and encoded output using openssl :( is there any way to decode the key

astraw38 commented 2 years ago

Private keys can't wrap. I guess technically since RSA private keys contain all the info to recreate a public key you could 'use' it to wrap an AES key, but not with a Luna HSM. Private keys don't have the CKA_WRAP attribute set, and can't have it set. Remember wrapping a key is basically encrypting it and excluding a few specific mechanism that do a derive and wrap, private keys can't be used for encryption - - only decryption

Perhaps detailed output of what you tried to do might help?

sinu commented 2 years ago

@astraw38 please see the step by step replication

**using openssl  generating rsa keys** 

openssl genrsa -out private.pem
openssl rsa -in private.pem -pubout -out public.pem

[root@rsatest]# ls -lrth     (RSA KEYS)
total 20K
-rw------- 1 root root 1.7K May 30 15:06 private.pem
-rw-r--r-- 1 root root  451 May 30 15:06 public.pem

Importing public key to hsm

[root@dddrsatest]# /root/dpod/bin/64/cmu import -pubkey=rsa -in public.pem Certificate Management Utility (64-bit) v10.4.0-417. Copyright (c) 2021 SafeNet. All rights reserved.

Please enter password for token in slot 3 : ****

...The RSA public key object was successfully created -> handle(1)

HSM OUTPUT

slot number: 3 serial number: 1392941700253 Handle Label Key Type Class Attributes


1 RSA PUBLIC_KEY -----v-MT--P 2 Unwrapped AES Key AES SECRET_KEY edwusvXMTSRP 3 Generated AES Key AES SECRET_KEY edwusvXMTSRP 4 san_pyhsm_test2 AES SECRET_KEY edwusvXMTSRP 5 san_pyhsm_test AES SECRET_KEY edwusvXMTSRP 6 Derived Key From Base AES SECRET_KEY edwusvXMTSRP

after attribute change

slot number: 3 serial number: 1392941700253 Handle Label Key Type Class Attributes


1 PUBRSAKEY RSA PUBLIC_KEY e-w--v-MT--P 2 Unwrapped AES Key AES SECRET_KEY edwusvXMTSRP 3 Generated AES Key AES SECRET_KEY edwusvXMTSRP 4 san_pyhsm_test2 AES SECRET_KEY edwusvXMTSRP 5 san_pyhsm_test AES SECRET_KEY edwusvXMTSRP 6 Derived Key From Base AES SECRET_KEY edwusvXMTSRP 7 IDSMASTER AES SECRET_KEY edwusvXMTSRP

Wrapping AES key with RSA public key

Status: Doing great, no errors (CKR_OK) Enter your choice : 60 [1]DES-ECB [2]DES-CBC [3]DES3-ECB [4]DES3-CBC [7]CAST3-ECB [8]CAST3-CBC [9]RSA [10]TRANSLA [11]DES3-CBC-PAD [12]DES3-CBC-PAD-IPSEC [13]SEED-ECB [14]SEED-CBC [15]SEED-CBC-PAD [16]DES-CBC-PAD [17]CAST3-CBC-PAD [18]CAST5-CBC-PAD [19]AES-ECB [20]AES-CBC [21]AES-CBC-PAD [22]AES-CBC-PAD-IPSEC [23]ARIA-ECB [24]ARIA-CBC [25]ARIA-CBC-PAD [26]RSA_OAEP [27]SET_OAEP [28]AES-CTR [29]DES3-CTR [30]AES-KW [31]AES-KWP [34]AES-KEY-WRAP [35]AES-GCM Select mechanism for wrapping: 26

Enter filename of OAEP Source Data [0 for none]: 0

Enter ouid of wrapping key (0 to list available objects) : 89d0000026000001ef990800

Enter ouid of key to wrap (0 to list available objects) : b22600000b000002f5ec0900

Wrapped key was saved in file wrapped.key

[root@nsstower rsatest]# ls -lrth total 24K -rw------- 1 root root 1.7K May 30 15:06 private.pem -rw-r--r-- 1 root root 451 May 30 15:06 public.pem -rw-r--r-- 1 root root 256 May 30 15:52 wrapped.key

Now you can see wrapped keys are exported to local but how do i open wrapped.key to see the AES key ?

astraw38 commented 2 years ago

You'd need to use your RSA private key to decrypt wrapped.key

sinu commented 2 years ago

@astraw38 thanks for the update, i was able to decrypt it . can we use NIST_SP800_108_KDF_CM_SHA256 (SHA256 as PRF) for deriving key?

astraw38 commented 2 years ago

I believe so, might be as part of PKCS11 3.0 spec. You can find mechanism parameters, constants and such in the spec: https://docs.oasis-open.org/pkcs11/pkcs11-curr/v3.0/os/pkcs11-curr-v3.0-os.html#_Toc30061446. You'd have to check Luna release notes to see if what you're looking for is supported, or simply check your supported mechanism list (C_GetMechanismList, I believe there's helpers for that in ckdemo & lunacm).